Rollup merge of #128045 - pnkfelix:rustc-contracts, r=oli-obk
#[contracts::requires(...)] + #[contracts::ensures(...)] cc https://github.com/rust-lang/rust/issues/128044 Updated contract support: attribute syntax for preconditions and postconditions, implemented via a series of desugarings that culminates in: 1. a compile-time flag (`-Z contract-checks`) that, similar to `-Z ub-checks`, attempts to ensure that the decision of enabling/disabling contract checks is delayed until the end user program is compiled, 2. invocations of lang-items that handle invoking the precondition, building a checker for the post-condition, and invoking that post-condition checker at the return sites for the function, and 3. intrinsics for the actual evaluation of pre- and post-condition predicates that third-party verification tools can intercept and reinterpret for their own purposes (e.g. creating shims of behavior that abstract away the function body and replace it solely with the pre- and post-conditions). Known issues: * My original intent, as described in the MCP (https://github.com/rust-lang/compiler-team/issues/759) was to have a rustc-prefixed attribute namespace (like rustc_contracts::requires). But I could not get things working when I tried to do rewriting via a rustc-prefixed builtin attribute-macro. So for now it is called `contracts::requires`. * Our attribute macro machinery does not provide direct support for attribute arguments that are parsed like rust expressions. I spent some time trying to add that (e.g. something that would parse the attribute arguments as an AST while treating the remainder of the items as a token-tree), but its too big a lift for me to undertake. So instead I hacked in something approximating that goal, by semi-trivially desugaring the token-tree attribute contents into internal AST constucts. This may be too fragile for the long-term. * (In particular, it *definitely* breaks when you try to add a contract to a function like this: `fn foo1(x: i32) -> S<{ 23 }> { ... }`, because its token-tree based search for where to inject the internal AST constructs cannot immediately see that the `{ 23 }` is within a generics list. I think we can live for this for the short-term, i.e. land the work, and continue working on it while in parallel adding a new attribute variant that takes a token-tree attribute alongside an AST annotation, which would completely resolve the issue here.) * the *intent* of `-Z contract-checks` is that it behaves like `-Z ub-checks`, in that we do not prematurely commit to including or excluding the contract evaluation in upstream crates (most notably, `core` and `std`). But the current test suite does not actually *check* that this is the case. Ideally the test suite would be extended with a multi-crate test that explores the matrix of enabling/disabling contracts on both the upstream lib and final ("leaf") bin crates.
This commit is contained in:
commit
d81701b610
123 changed files with 1869 additions and 39 deletions
|
@ -3348,11 +3348,18 @@ pub struct Impl {
|
||||||
pub items: ThinVec<P<AssocItem>>,
|
pub items: ThinVec<P<AssocItem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Encodable, Decodable, Debug, Default)]
|
||||||
|
pub struct FnContract {
|
||||||
|
pub requires: Option<P<Expr>>,
|
||||||
|
pub ensures: Option<P<Expr>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Encodable, Decodable, Debug)]
|
#[derive(Clone, Encodable, Decodable, Debug)]
|
||||||
pub struct Fn {
|
pub struct Fn {
|
||||||
pub defaultness: Defaultness,
|
pub defaultness: Defaultness,
|
||||||
pub generics: Generics,
|
pub generics: Generics,
|
||||||
pub sig: FnSig,
|
pub sig: FnSig,
|
||||||
|
pub contract: Option<P<FnContract>>,
|
||||||
pub body: Option<P<Block>>,
|
pub body: Option<P<Block>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3650,7 +3657,7 @@ mod size_asserts {
|
||||||
static_assert_size!(Block, 32);
|
static_assert_size!(Block, 32);
|
||||||
static_assert_size!(Expr, 72);
|
static_assert_size!(Expr, 72);
|
||||||
static_assert_size!(ExprKind, 40);
|
static_assert_size!(ExprKind, 40);
|
||||||
static_assert_size!(Fn, 160);
|
static_assert_size!(Fn, 168);
|
||||||
static_assert_size!(ForeignItem, 88);
|
static_assert_size!(ForeignItem, 88);
|
||||||
static_assert_size!(ForeignItemKind, 16);
|
static_assert_size!(ForeignItemKind, 16);
|
||||||
static_assert_size!(GenericArg, 24);
|
static_assert_size!(GenericArg, 24);
|
||||||
|
|
|
@ -143,6 +143,10 @@ pub trait MutVisitor: Sized {
|
||||||
walk_flat_map_assoc_item(self, i, ctxt)
|
walk_flat_map_assoc_item(self, i, ctxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_contract(&mut self, c: &mut P<FnContract>) {
|
||||||
|
walk_contract(self, c);
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_fn_decl(&mut self, d: &mut P<FnDecl>) {
|
fn visit_fn_decl(&mut self, d: &mut P<FnDecl>) {
|
||||||
walk_fn_decl(self, d);
|
walk_fn_decl(self, d);
|
||||||
}
|
}
|
||||||
|
@ -958,13 +962,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
|
||||||
_ctxt,
|
_ctxt,
|
||||||
_ident,
|
_ident,
|
||||||
_vis,
|
_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.
|
// Identifier and visibility are visited as a part of the item.
|
||||||
visit_defaultness(vis, defaultness);
|
visit_defaultness(vis, defaultness);
|
||||||
vis.visit_fn_header(header);
|
vis.visit_fn_header(header);
|
||||||
vis.visit_generics(generics);
|
vis.visit_generics(generics);
|
||||||
vis.visit_fn_decl(decl);
|
vis.visit_fn_decl(decl);
|
||||||
|
if let Some(contract) = contract {
|
||||||
|
vis.visit_contract(contract);
|
||||||
|
}
|
||||||
if let Some(body) = body {
|
if let Some(body) = body {
|
||||||
vis.visit_block(body);
|
vis.visit_block(body);
|
||||||
}
|
}
|
||||||
|
@ -979,6 +986,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn walk_contract<T: MutVisitor>(vis: &mut T, contract: &mut P<FnContract>) {
|
||||||
|
let FnContract { requires, 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<T: MutVisitor>(vis: &mut T, decl: &mut P<FnDecl>) {
|
fn walk_fn_decl<T: MutVisitor>(vis: &mut T, decl: &mut P<FnDecl>) {
|
||||||
let FnDecl { inputs, output } = decl.deref_mut();
|
let FnDecl { inputs, output } = decl.deref_mut();
|
||||||
inputs.flat_map_in_place(|param| vis.flat_map_param(param));
|
inputs.flat_map_in_place(|param| vis.flat_map_param(param));
|
||||||
|
|
|
@ -188,6 +188,9 @@ pub trait Visitor<'ast>: Sized {
|
||||||
fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result {
|
fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result {
|
||||||
walk_closure_binder(self, b)
|
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 {
|
fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result {
|
||||||
walk_where_predicate(self, p)
|
walk_where_predicate(self, p)
|
||||||
}
|
}
|
||||||
|
@ -800,6 +803,17 @@ pub fn walk_closure_binder<'a, V: Visitor<'a>>(
|
||||||
V::Result::output()
|
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>>(
|
pub fn walk_where_predicate<'a, V: Visitor<'a>>(
|
||||||
visitor: &mut V,
|
visitor: &mut V,
|
||||||
predicate: &'a WherePredicate,
|
predicate: &'a WherePredicate,
|
||||||
|
@ -862,12 +876,13 @@ pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Resu
|
||||||
_ctxt,
|
_ctxt,
|
||||||
_ident,
|
_ident,
|
||||||
_vis,
|
_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.
|
// Identifier and visibility are visited as a part of the item.
|
||||||
try_visit!(visitor.visit_fn_header(header));
|
try_visit!(visitor.visit_fn_header(header));
|
||||||
try_visit!(visitor.visit_generics(generics));
|
try_visit!(visitor.visit_generics(generics));
|
||||||
try_visit!(visitor.visit_fn_decl(decl));
|
try_visit!(visitor.visit_fn_decl(decl));
|
||||||
|
visit_opt!(visitor, visit_contract, contract);
|
||||||
visit_opt!(visitor, visit_block, body);
|
visit_opt!(visitor, visit_block, body);
|
||||||
}
|
}
|
||||||
FnKind::Closure(binder, coroutine_kind, decl, body) => {
|
FnKind::Closure(binder, coroutine_kind, decl, body) => {
|
||||||
|
|
|
@ -311,8 +311,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||||
hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label))
|
hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label))
|
||||||
}
|
}
|
||||||
ExprKind::Ret(e) => {
|
ExprKind::Ret(e) => {
|
||||||
let e = e.as_ref().map(|x| self.lower_expr(x));
|
let expr = e.as_ref().map(|x| self.lower_expr(x));
|
||||||
hir::ExprKind::Ret(e)
|
self.checked_return(expr)
|
||||||
}
|
}
|
||||||
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
|
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
|
||||||
ExprKind::Become(sub_expr) => {
|
ExprKind::Become(sub_expr) => {
|
||||||
|
@ -379,6 +379,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 {
|
pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock {
|
||||||
self.with_new_scopes(c.value.span, |this| {
|
self.with_new_scopes(c.value.span, |this| {
|
||||||
let def_id = this.local_def_id(c.id);
|
let def_id = this.local_def_id(c.id);
|
||||||
|
@ -1991,7 +2017,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
} else {
|
} 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);
|
self.lower_attrs(ret_expr.hir_id, &attrs);
|
||||||
|
|
||||||
|
@ -2040,7 +2067,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||||
let target_id = Ok(catch_id);
|
let target_id = Ok(catch_id);
|
||||||
hir::ExprKind::Break(hir::Destination { label: None, target_id }, Some(from_yeet_expr))
|
hir::ExprKind::Break(hir::Destination { label: None, target_id }, Some(from_yeet_expr))
|
||||||
} else {
|
} else {
|
||||||
hir::ExprKind::Ret(Some(from_yeet_expr))
|
self.checked_return(Some(from_yeet_expr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,9 +207,40 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||||
sig: FnSig { decl, header, span: fn_sig_span },
|
sig: FnSig { decl, header, span: fn_sig_span },
|
||||||
generics,
|
generics,
|
||||||
body,
|
body,
|
||||||
|
contract,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
self.with_new_scopes(*fn_sig_span, |this| {
|
self.with_new_scopes(*fn_sig_span, |this| {
|
||||||
|
assert!(this.contract.is_none());
|
||||||
|
if let Some(contract) = contract {
|
||||||
|
let requires = contract.requires.clone();
|
||||||
|
let ensures = contract.ensures.clone();
|
||||||
|
let ensures = ensures.map(|ens| {
|
||||||
|
// FIXME: this needs to be a fresh (or illegal) identifier to prevent
|
||||||
|
// accidental capture of a parameter or global variable.
|
||||||
|
let checker_ident: Ident =
|
||||||
|
Ident::from_str_and_span("__ensures_checker", ens.span);
|
||||||
|
let (checker_pat, checker_hir_id) = this.pat_ident_binding_mode_mut(
|
||||||
|
ens.span,
|
||||||
|
checker_ident,
|
||||||
|
hir::BindingMode::NONE,
|
||||||
|
);
|
||||||
|
|
||||||
|
crate::FnContractLoweringEnsures {
|
||||||
|
expr: ens,
|
||||||
|
fresh_ident: (checker_ident, checker_pat, checker_hir_id),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note: `with_new_scopes` will reinstall the outer
|
||||||
|
// item's contract (if any) after its callback finishes.
|
||||||
|
this.contract.replace(crate::FnContractLoweringInfo {
|
||||||
|
span,
|
||||||
|
requires,
|
||||||
|
ensures,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Note: we don't need to change the return type from `T` to
|
// Note: we don't need to change the return type from `T` to
|
||||||
// `impl Future<Output = T>` here because lower_body
|
// `impl Future<Output = T>` here because lower_body
|
||||||
// only cares about the input argument patterns in the function
|
// only cares about the input argument patterns in the function
|
||||||
|
@ -1054,10 +1085,64 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||||
body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
|
body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
|
||||||
) -> hir::BodyId {
|
) -> hir::BodyId {
|
||||||
self.lower_body(|this| {
|
self.lower_body(|this| {
|
||||||
(
|
let params =
|
||||||
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))),
|
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x)));
|
||||||
body(this),
|
let result = body(this);
|
||||||
)
|
|
||||||
|
let opt_contract = this.contract.take();
|
||||||
|
|
||||||
|
// { body }
|
||||||
|
// ==>
|
||||||
|
// { contract_requires(PRECOND); { body } }
|
||||||
|
let Some(contract) = opt_contract else { return (params, result) };
|
||||||
|
let result_ref = this.arena.alloc(result);
|
||||||
|
let lit_unit = |this: &mut LoweringContext<'_, 'hir>| {
|
||||||
|
this.expr(contract.span, hir::ExprKind::Tup(&[]))
|
||||||
|
};
|
||||||
|
|
||||||
|
let precond: hir::Stmt<'hir> = if let Some(req) = contract.requires {
|
||||||
|
let lowered_req = this.lower_expr_mut(&req);
|
||||||
|
let precond = this.expr_call_lang_item_fn_mut(
|
||||||
|
req.span,
|
||||||
|
hir::LangItem::ContractCheckRequires,
|
||||||
|
&*arena_vec![this; lowered_req],
|
||||||
|
);
|
||||||
|
this.stmt_expr(req.span, precond)
|
||||||
|
} else {
|
||||||
|
let u = lit_unit(this);
|
||||||
|
this.stmt_expr(contract.span, u)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (postcond_checker, result) = if let Some(ens) = contract.ensures {
|
||||||
|
let crate::FnContractLoweringEnsures { expr: ens, fresh_ident } = ens;
|
||||||
|
let lowered_ens: hir::Expr<'hir> = this.lower_expr_mut(&ens);
|
||||||
|
let postcond_checker = this.expr_call_lang_item_fn(
|
||||||
|
ens.span,
|
||||||
|
hir::LangItem::ContractBuildCheckEnsures,
|
||||||
|
&*arena_vec![this; lowered_ens],
|
||||||
|
);
|
||||||
|
let checker_binding_pat = fresh_ident.1;
|
||||||
|
(
|
||||||
|
this.stmt_let_pat(
|
||||||
|
None,
|
||||||
|
ens.span,
|
||||||
|
Some(postcond_checker),
|
||||||
|
this.arena.alloc(checker_binding_pat),
|
||||||
|
hir::LocalSource::Contract,
|
||||||
|
),
|
||||||
|
this.inject_ensures_check(result_ref, ens.span, fresh_ident.0, fresh_ident.2),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let u = lit_unit(this);
|
||||||
|
(this.stmt_expr(contract.span, u), &*result_ref)
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = this.block_all(
|
||||||
|
contract.span,
|
||||||
|
arena_vec![this; precond, postcond_checker],
|
||||||
|
Some(result),
|
||||||
|
);
|
||||||
|
(params, this.expr_block(block))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,19 @@ mod path;
|
||||||
|
|
||||||
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
|
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct FnContractLoweringInfo<'hir> {
|
||||||
|
pub span: Span,
|
||||||
|
pub requires: Option<ast::ptr::P<ast::Expr>>,
|
||||||
|
pub ensures: Option<FnContractLoweringEnsures<'hir>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct FnContractLoweringEnsures<'hir> {
|
||||||
|
expr: ast::ptr::P<ast::Expr>,
|
||||||
|
fresh_ident: (Ident, hir::Pat<'hir>, HirId),
|
||||||
|
}
|
||||||
|
|
||||||
struct LoweringContext<'a, 'hir> {
|
struct LoweringContext<'a, 'hir> {
|
||||||
tcx: TyCtxt<'hir>,
|
tcx: TyCtxt<'hir>,
|
||||||
resolver: &'a mut ResolverAstLowering,
|
resolver: &'a mut ResolverAstLowering,
|
||||||
|
@ -100,6 +113,8 @@ struct LoweringContext<'a, 'hir> {
|
||||||
/// Collect items that were created by lowering the current owner.
|
/// Collect items that were created by lowering the current owner.
|
||||||
children: Vec<(LocalDefId, hir::MaybeOwner<'hir>)>,
|
children: Vec<(LocalDefId, hir::MaybeOwner<'hir>)>,
|
||||||
|
|
||||||
|
contract: Option<FnContractLoweringInfo<'hir>>,
|
||||||
|
|
||||||
coroutine_kind: Option<hir::CoroutineKind>,
|
coroutine_kind: Option<hir::CoroutineKind>,
|
||||||
|
|
||||||
/// When inside an `async` context, this is the `HirId` of the
|
/// When inside an `async` context, this is the `HirId` of the
|
||||||
|
@ -148,6 +163,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
bodies: Vec::new(),
|
bodies: Vec::new(),
|
||||||
attrs: SortedMap::default(),
|
attrs: SortedMap::default(),
|
||||||
children: Vec::default(),
|
children: Vec::default(),
|
||||||
|
contract: None,
|
||||||
current_hir_id_owner: hir::CRATE_OWNER_ID,
|
current_hir_id_owner: hir::CRATE_OWNER_ID,
|
||||||
item_local_id_counter: hir::ItemLocalId::ZERO,
|
item_local_id_counter: hir::ItemLocalId::ZERO,
|
||||||
ident_and_label_to_local_id: Default::default(),
|
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;
|
let was_in_loop_condition = self.is_in_loop_condition;
|
||||||
self.is_in_loop_condition = false;
|
self.is_in_loop_condition = false;
|
||||||
|
|
||||||
|
let old_contract = self.contract.take();
|
||||||
|
|
||||||
let catch_scope = self.catch_scope.take();
|
let catch_scope = self.catch_scope.take();
|
||||||
let loop_scope = self.loop_scope.take();
|
let loop_scope = self.loop_scope.take();
|
||||||
let ret = f(self);
|
let ret = f(self);
|
||||||
self.catch_scope = catch_scope;
|
self.catch_scope = catch_scope;
|
||||||
self.loop_scope = loop_scope;
|
self.loop_scope = loop_scope;
|
||||||
|
|
||||||
|
self.contract = old_contract;
|
||||||
|
|
||||||
self.is_in_loop_condition = was_in_loop_condition;
|
self.is_in_loop_condition = was_in_loop_condition;
|
||||||
|
|
||||||
self.current_item = current_item;
|
self.current_item = current_item;
|
||||||
|
|
|
@ -917,7 +917,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
|
||||||
walk_list!(self, visit_attribute, &item.attrs);
|
walk_list!(self, visit_attribute, &item.attrs);
|
||||||
return; // Avoid visiting again.
|
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);
|
self.check_defaultness(item.span, *defaultness);
|
||||||
|
|
||||||
let is_intrinsic =
|
let is_intrinsic =
|
||||||
|
|
|
@ -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!(pin_ergonomics, "pinned reference syntax is experimental");
|
||||||
gate_all!(unsafe_fields, "`unsafe` fields are experimental");
|
gate_all!(unsafe_fields, "`unsafe` fields are experimental");
|
||||||
gate_all!(unsafe_binders, "unsafe binder types are experimental");
|
gate_all!(unsafe_binders, "unsafe binder types are experimental");
|
||||||
|
gate_all!(contracts, "contracts are incomplete");
|
||||||
|
gate_all!(contracts_internals, "contract internal machinery is for internal use only");
|
||||||
|
|
||||||
if !visitor.features.never_patterns() {
|
if !visitor.features.never_patterns() {
|
||||||
if let Some(spans) = spans.get(&sym::never_patterns) {
|
if let Some(spans) = spans.get(&sym::never_patterns) {
|
||||||
|
|
|
@ -650,13 +650,17 @@ impl<'a> State<'a> {
|
||||||
attrs: &[ast::Attribute],
|
attrs: &[ast::Attribute],
|
||||||
func: &ast::Fn,
|
func: &ast::Fn,
|
||||||
) {
|
) {
|
||||||
let ast::Fn { defaultness, generics, sig, body } = func;
|
let ast::Fn { defaultness, generics, sig, contract, body } = func;
|
||||||
if body.is_some() {
|
if body.is_some() {
|
||||||
self.head("");
|
self.head("");
|
||||||
}
|
}
|
||||||
self.print_visibility(vis);
|
self.print_visibility(vis);
|
||||||
self.print_defaultness(*defaultness);
|
self.print_defaultness(*defaultness);
|
||||||
self.print_fn(&sig.decl, sig.header, Some(name), generics);
|
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 {
|
if let Some(body) = body {
|
||||||
self.nbsp();
|
self.nbsp();
|
||||||
self.print_block_with_attrs(body, attrs);
|
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(
|
pub(crate) fn print_fn(
|
||||||
&mut self,
|
&mut self,
|
||||||
decl: &ast::FnDecl,
|
decl: &ast::FnDecl,
|
||||||
|
|
|
@ -1653,6 +1653,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
||||||
ConstraintCategory::SizedBound,
|
ConstraintCategory::SizedBound,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
&Rvalue::NullaryOp(NullOp::ContractChecks, _) => {}
|
||||||
&Rvalue::NullaryOp(NullOp::UbChecks, _) => {}
|
&Rvalue::NullaryOp(NullOp::UbChecks, _) => {}
|
||||||
|
|
||||||
Rvalue::ShallowInitBox(operand, ty) => {
|
Rvalue::ShallowInitBox(operand, ty) => {
|
||||||
|
|
|
@ -85,6 +85,7 @@ fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span
|
||||||
defaultness: ast::Defaultness::Final,
|
defaultness: ast::Defaultness::Final,
|
||||||
sig,
|
sig,
|
||||||
generics: Generics::default(),
|
generics: Generics::default(),
|
||||||
|
contract: None,
|
||||||
body,
|
body,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
176
compiler/rustc_builtin_macros/src/contracts.rs
Normal file
176
compiler/rustc_builtin_macros/src/contracts.rs
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
#![allow(unused_imports, unused_variables)]
|
||||||
|
|
||||||
|
use rustc_ast::token;
|
||||||
|
use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
|
||||||
|
use rustc_errors::ErrorGuaranteed;
|
||||||
|
use rustc_expand::base::{AttrProcMacro, ExtCtxt};
|
||||||
|
use rustc_span::Span;
|
||||||
|
use rustc_span::symbol::{Ident, Symbol, kw, sym};
|
||||||
|
|
||||||
|
pub(crate) struct ExpandRequires;
|
||||||
|
|
||||||
|
pub(crate) struct ExpandEnsures;
|
||||||
|
|
||||||
|
impl AttrProcMacro for ExpandRequires {
|
||||||
|
fn expand<'cx>(
|
||||||
|
&self,
|
||||||
|
ecx: &'cx mut ExtCtxt<'_>,
|
||||||
|
span: Span,
|
||||||
|
annotation: TokenStream,
|
||||||
|
annotated: TokenStream,
|
||||||
|
) -> Result<TokenStream, ErrorGuaranteed> {
|
||||||
|
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<TokenStream, ErrorGuaranteed> {
|
||||||
|
expand_ensures_tts(ecx, span, annotation, annotated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand the function signature to include the contract clause.
|
||||||
|
///
|
||||||
|
/// The contracts clause will be injected before the function body and the optional where clause.
|
||||||
|
/// For that, we search for the body / where token, and invoke the `inject` callback to generate the
|
||||||
|
/// contract clause in the right place.
|
||||||
|
///
|
||||||
|
// FIXME: this kind of manual token tree munging does not have significant precedent among
|
||||||
|
// rustc builtin macros, probably because most builtin macros use direct AST manipulation to
|
||||||
|
// accomplish similar goals. But since our attributes need to take arbitrary expressions, and
|
||||||
|
// our attribute infrastructure does not yet support mixing a token-tree annotation with an AST
|
||||||
|
// annotated, we end up doing token tree manipulation.
|
||||||
|
fn expand_contract_clause(
|
||||||
|
ecx: &mut ExtCtxt<'_>,
|
||||||
|
attr_span: Span,
|
||||||
|
annotated: TokenStream,
|
||||||
|
inject: impl FnOnce(&mut TokenStream) -> Result<(), ErrorGuaranteed>,
|
||||||
|
) -> Result<TokenStream, ErrorGuaranteed> {
|
||||||
|
let mut new_tts = TokenStream::default();
|
||||||
|
let mut cursor = annotated.iter();
|
||||||
|
|
||||||
|
let is_kw = |tt: &TokenTree, sym: Symbol| {
|
||||||
|
if let TokenTree::Token(token, _) = tt { token.is_ident_named(sym) } else { false }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the `fn` keyword to check if this is a function.
|
||||||
|
if cursor
|
||||||
|
.find(|tt| {
|
||||||
|
new_tts.push_tree((*tt).clone());
|
||||||
|
is_kw(tt, kw::Fn)
|
||||||
|
})
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
return Err(ecx
|
||||||
|
.sess
|
||||||
|
.dcx()
|
||||||
|
.span_err(attr_span, "contract annotations can only be used on functions"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found the `fn` keyword, now find either the `where` token or the function body.
|
||||||
|
let next_tt = loop {
|
||||||
|
let Some(tt) = cursor.next() else {
|
||||||
|
return Err(ecx.sess.dcx().span_err(
|
||||||
|
attr_span,
|
||||||
|
"contract annotations is only supported in functions with bodies",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
// If `tt` is the last element. Check if it is the function body.
|
||||||
|
if cursor.peek().is_none() {
|
||||||
|
if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = tt {
|
||||||
|
break tt;
|
||||||
|
} else {
|
||||||
|
return Err(ecx.sess.dcx().span_err(
|
||||||
|
attr_span,
|
||||||
|
"contract annotations is only supported in functions with bodies",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_kw(tt, kw::Where) {
|
||||||
|
break tt;
|
||||||
|
}
|
||||||
|
new_tts.push_tree(tt.clone());
|
||||||
|
};
|
||||||
|
|
||||||
|
// At this point, we've transcribed everything from the `fn` through the formal parameter list
|
||||||
|
// and return type declaration, (if any), but `tt` itself has *not* been transcribed.
|
||||||
|
//
|
||||||
|
// Now inject the AST contract form.
|
||||||
|
//
|
||||||
|
inject(&mut new_tts)?;
|
||||||
|
|
||||||
|
// Above we injected the internal AST requires/ensures construct. Now copy over all the other
|
||||||
|
// token trees.
|
||||||
|
new_tts.push_tree(next_tt.clone());
|
||||||
|
while let Some(tt) = cursor.next() {
|
||||||
|
new_tts.push_tree(tt.clone());
|
||||||
|
if cursor.peek().is_none()
|
||||||
|
&& !matches!(tt, TokenTree::Delimited(_, _, token::Delimiter::Brace, _))
|
||||||
|
{
|
||||||
|
return Err(ecx.sess.dcx().span_err(
|
||||||
|
attr_span,
|
||||||
|
"contract annotations is only supported in functions with bodies",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the span as a contract attribute expansion.
|
||||||
|
// This is used later to stop users from using the extended syntax directly
|
||||||
|
// which is gated via `contracts_internals`.
|
||||||
|
ecx.psess().contract_attribute_spans.push(attr_span);
|
||||||
|
|
||||||
|
Ok(new_tts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_requires_tts(
|
||||||
|
_ecx: &mut ExtCtxt<'_>,
|
||||||
|
attr_span: Span,
|
||||||
|
annotation: TokenStream,
|
||||||
|
annotated: TokenStream,
|
||||||
|
) -> Result<TokenStream, ErrorGuaranteed> {
|
||||||
|
expand_contract_clause(_ecx, attr_span, annotated, |new_tts| {
|
||||||
|
new_tts.push_tree(TokenTree::Token(
|
||||||
|
token::Token::from_ast_ident(Ident::new(kw::ContractRequires, attr_span)),
|
||||||
|
Spacing::Joint,
|
||||||
|
));
|
||||||
|
new_tts.push_tree(TokenTree::Token(
|
||||||
|
token::Token::new(token::TokenKind::OrOr, attr_span),
|
||||||
|
Spacing::Alone,
|
||||||
|
));
|
||||||
|
new_tts.push_tree(TokenTree::Delimited(
|
||||||
|
DelimSpan::from_single(attr_span),
|
||||||
|
DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden),
|
||||||
|
token::Delimiter::Parenthesis,
|
||||||
|
annotation,
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_ensures_tts(
|
||||||
|
_ecx: &mut ExtCtxt<'_>,
|
||||||
|
attr_span: Span,
|
||||||
|
annotation: TokenStream,
|
||||||
|
annotated: TokenStream,
|
||||||
|
) -> Result<TokenStream, ErrorGuaranteed> {
|
||||||
|
expand_contract_clause(_ecx, attr_span, annotated, |new_tts| {
|
||||||
|
new_tts.push_tree(TokenTree::Token(
|
||||||
|
token::Token::from_ast_ident(Ident::new(kw::ContractEnsures, attr_span)),
|
||||||
|
Spacing::Joint,
|
||||||
|
));
|
||||||
|
new_tts.push_tree(TokenTree::Delimited(
|
||||||
|
DelimSpan::from_single(attr_span),
|
||||||
|
DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden),
|
||||||
|
token::Delimiter::Parenthesis,
|
||||||
|
annotation,
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
|
@ -1034,6 +1034,7 @@ impl<'a> MethodDef<'a> {
|
||||||
defaultness,
|
defaultness,
|
||||||
sig,
|
sig,
|
||||||
generics: fn_generics,
|
generics: fn_generics,
|
||||||
|
contract: None,
|
||||||
body: Some(body_block),
|
body: Some(body_block),
|
||||||
})),
|
})),
|
||||||
tokens: None,
|
tokens: None,
|
||||||
|
|
|
@ -81,6 +81,7 @@ impl AllocFnFactory<'_, '_> {
|
||||||
defaultness: ast::Defaultness::Final,
|
defaultness: ast::Defaultness::Final,
|
||||||
sig,
|
sig,
|
||||||
generics: Generics::default(),
|
generics: Generics::default(),
|
||||||
|
contract: None,
|
||||||
body,
|
body,
|
||||||
}));
|
}));
|
||||||
let item = self.cx.item(
|
let item = self.cx.item(
|
||||||
|
|
|
@ -55,6 +55,7 @@ mod trace_macros;
|
||||||
|
|
||||||
pub mod asm;
|
pub mod asm;
|
||||||
pub mod cmdline_attrs;
|
pub mod cmdline_attrs;
|
||||||
|
pub mod contracts;
|
||||||
pub mod proc_macro_harness;
|
pub mod proc_macro_harness;
|
||||||
pub mod standard_library_imports;
|
pub mod standard_library_imports;
|
||||||
pub mod test_harness;
|
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);
|
let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
|
||||||
register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client })));
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -344,6 +344,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
|
||||||
defaultness,
|
defaultness,
|
||||||
sig,
|
sig,
|
||||||
generics: ast::Generics::default(),
|
generics: ast::Generics::default(),
|
||||||
|
contract: None,
|
||||||
body: Some(main_body),
|
body: Some(main_body),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -868,7 +868,16 @@ fn codegen_stmt<'tcx>(
|
||||||
NullOp::UbChecks => {
|
NullOp::UbChecks => {
|
||||||
let val = fx.tcx.sess.ub_checks();
|
let val = fx.tcx.sess.ub_checks();
|
||||||
let val = CValue::by_val(
|
let val = CValue::by_val(
|
||||||
fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()),
|
fx.bcx.ins().iconst(types::I8, i64::from(val)),
|
||||||
|
fx.layout_of(fx.tcx.types.bool),
|
||||||
|
);
|
||||||
|
lval.write_cvalue(fx, val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NullOp::ContractChecks => {
|
||||||
|
let val = fx.tcx.sess.contract_checks();
|
||||||
|
let val = CValue::by_val(
|
||||||
|
fx.bcx.ins().iconst(types::I8, i64::from(val)),
|
||||||
fx.layout_of(fx.tcx.types.bool),
|
fx.layout_of(fx.tcx.types.bool),
|
||||||
);
|
);
|
||||||
lval.write_cvalue(fx, val);
|
lval.write_cvalue(fx, val);
|
||||||
|
|
|
@ -741,6 +741,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
let val = bx.tcx().sess.ub_checks();
|
let val = bx.tcx().sess.ub_checks();
|
||||||
bx.cx().const_bool(val)
|
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();
|
let tcx = self.cx.tcx();
|
||||||
OperandRef {
|
OperandRef {
|
||||||
|
|
|
@ -675,7 +675,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||||
Rvalue::Cast(_, _, _) => {}
|
Rvalue::Cast(_, _, _) => {}
|
||||||
|
|
||||||
Rvalue::NullaryOp(
|
Rvalue::NullaryOp(
|
||||||
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks,
|
NullOp::SizeOf
|
||||||
|
| NullOp::AlignOf
|
||||||
|
| NullOp::OffsetOf(_)
|
||||||
|
| NullOp::UbChecks
|
||||||
|
| NullOp::ContractChecks,
|
||||||
_,
|
_,
|
||||||
) => {}
|
) => {}
|
||||||
Rvalue::ShallowInitBox(_, _) => {}
|
Rvalue::ShallowInitBox(_, _) => {}
|
||||||
|
|
|
@ -293,6 +293,9 @@ pub trait Machine<'tcx>: Sized {
|
||||||
/// Determines the result of a `NullaryOp::UbChecks` invocation.
|
/// Determines the result of a `NullaryOp::UbChecks` invocation.
|
||||||
fn ub_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>;
|
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.
|
/// Called when the interpreter encounters a `StatementKind::ConstEvalCounter` instruction.
|
||||||
/// You can use this to detect long or endlessly running programs.
|
/// You can use this to detect long or endlessly running programs.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -679,6 +682,13 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
|
||||||
interp_ok(true)
|
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)]
|
#[inline(always)]
|
||||||
fn adjust_global_allocation<'b>(
|
fn adjust_global_allocation<'b>(
|
||||||
_ecx: &InterpCx<$tcx, Self>,
|
_ecx: &InterpCx<$tcx, Self>,
|
||||||
|
|
|
@ -537,6 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
ImmTy::from_uint(val, usize_layout())
|
ImmTy::from_uint(val, usize_layout())
|
||||||
}
|
}
|
||||||
UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx),
|
UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx),
|
||||||
|
ContractChecks => ImmTy::from_bool(M::contract_checks(self)?, *self.tcx),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ const GATED_CFGS: &[GatedCfg] = &[
|
||||||
// (name in cfg, feature, function to check if the feature is enabled)
|
// (name in cfg, feature, function to check if the feature is enabled)
|
||||||
(sym::overflow_checks, sym::cfg_overflow_checks, Features::cfg_overflow_checks),
|
(sym::overflow_checks, sym::cfg_overflow_checks, Features::cfg_overflow_checks),
|
||||||
(sym::ub_checks, sym::cfg_ub_checks, Features::cfg_ub_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_thread_local, sym::cfg_target_thread_local, Features::cfg_target_thread_local),
|
||||||
(
|
(
|
||||||
sym::target_has_atomic_equal_alignment,
|
sym::target_has_atomic_equal_alignment,
|
||||||
|
|
|
@ -403,6 +403,8 @@ declare_features! (
|
||||||
(unstable, c_variadic, "1.34.0", Some(44930)),
|
(unstable, c_variadic, "1.34.0", Some(44930)),
|
||||||
/// Allows the use of `#[cfg(<true/false>)]`.
|
/// Allows the use of `#[cfg(<true/false>)]`.
|
||||||
(unstable, cfg_boolean_literals, "1.83.0", Some(131204)),
|
(unstable, cfg_boolean_literals, "1.83.0", Some(131204)),
|
||||||
|
/// Allows the use of `#[cfg(contract_checks)` to check if contract checks are enabled.
|
||||||
|
(unstable, cfg_contract_checks, "CURRENT_RUSTC_VERSION", Some(128044)),
|
||||||
/// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour.
|
/// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour.
|
||||||
(unstable, cfg_overflow_checks, "1.71.0", Some(111466)),
|
(unstable, cfg_overflow_checks, "1.71.0", Some(111466)),
|
||||||
/// Provides the relocation model information as cfg entry
|
/// Provides the relocation model information as cfg entry
|
||||||
|
@ -445,6 +447,10 @@ declare_features! (
|
||||||
(unstable, const_trait_impl, "1.42.0", Some(67792)),
|
(unstable, const_trait_impl, "1.42.0", Some(67792)),
|
||||||
/// Allows the `?` operator in const contexts.
|
/// Allows the `?` operator in const contexts.
|
||||||
(unstable, const_try, "1.56.0", Some(74935)),
|
(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.
|
/// Allows coroutines to be cloned.
|
||||||
(unstable, coroutine_clone, "1.65.0", Some(95360)),
|
(unstable, coroutine_clone, "1.65.0", Some(95360)),
|
||||||
/// Allows defining coroutines.
|
/// Allows defining coroutines.
|
||||||
|
|
|
@ -2654,6 +2654,8 @@ pub enum LocalSource {
|
||||||
/// A desugared `expr = expr`, where the LHS is a tuple, struct, array or underscore expression.
|
/// A desugared `expr = expr`, where the LHS is a tuple, struct, array or underscore expression.
|
||||||
/// The span is that of the `=` sign.
|
/// The span is that of the `=` sign.
|
||||||
AssignDesugar(Span),
|
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 _ { .. }`.
|
/// Hints at the original code for a `match _ { .. }`.
|
||||||
|
|
|
@ -423,6 +423,10 @@ language_item_table! {
|
||||||
|
|
||||||
String, sym::String, string, Target::Struct, GenericRequirement::None;
|
String, sym::String, string, Target::Struct, GenericRequirement::None;
|
||||||
CStr, sym::CStr, c_str, 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 {
|
pub enum GenericRequirement {
|
||||||
|
|
|
@ -132,6 +132,9 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -
|
||||||
| sym::aggregate_raw_ptr
|
| sym::aggregate_raw_ptr
|
||||||
| sym::ptr_metadata
|
| sym::ptr_metadata
|
||||||
| sym::ub_checks
|
| sym::ub_checks
|
||||||
|
| sym::contract_checks
|
||||||
|
| sym::contract_check_requires
|
||||||
|
| sym::contract_check_ensures
|
||||||
| sym::fadd_algebraic
|
| sym::fadd_algebraic
|
||||||
| sym::fsub_algebraic
|
| sym::fsub_algebraic
|
||||||
| sym::fmul_algebraic
|
| sym::fmul_algebraic
|
||||||
|
@ -219,6 +222,16 @@ pub fn check_intrinsic_type(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(n_tps, 0, 0, inputs, output, hir::Safety::Unsafe)
|
(n_tps, 0, 0, inputs, output, hir::Safety::Unsafe)
|
||||||
|
} else if intrinsic_name == sym::contract_check_ensures {
|
||||||
|
// contract_check_ensures::<'a, Ret, C>(&'a Ret, C)
|
||||||
|
// where C: impl Fn(&'a Ret) -> bool,
|
||||||
|
//
|
||||||
|
// so: two type params, one lifetime param, 0 const params, two inputs, no return
|
||||||
|
|
||||||
|
let p = generics.param_at(0, tcx);
|
||||||
|
let r = ty::Region::new_early_param(tcx, p.to_early_bound_region_data());
|
||||||
|
let ref_ret = Ty::new_imm_ref(tcx, r, param(1));
|
||||||
|
(2, 1, 0, vec![ref_ret, param(2)], tcx.types.unit, hir::Safety::Safe)
|
||||||
} else {
|
} else {
|
||||||
let safety = intrinsic_operation_unsafety(tcx, intrinsic_id);
|
let safety = intrinsic_operation_unsafety(tcx, intrinsic_id);
|
||||||
let (n_tps, n_cts, inputs, output) = match intrinsic_name {
|
let (n_tps, n_cts, inputs, output) = match intrinsic_name {
|
||||||
|
@ -610,6 +623,11 @@ pub fn check_intrinsic_type(
|
||||||
|
|
||||||
sym::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))),
|
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>(C) -> bool, where C: impl Fn() -> bool
|
||||||
|
sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit),
|
||||||
|
|
||||||
sym::simd_eq
|
sym::simd_eq
|
||||||
| sym::simd_ne
|
| sym::simd_ne
|
||||||
| sym::simd_lt
|
| sym::simd_lt
|
||||||
|
|
|
@ -269,6 +269,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
// diverging expression (e.g. it arose from desugaring of `try { return }`),
|
// diverging expression (e.g. it arose from desugaring of `try { return }`),
|
||||||
// we skip issuing a warning because it is autogenerated code.
|
// we skip issuing a warning because it is autogenerated code.
|
||||||
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {}
|
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::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
|
||||||
ExprKind::MethodCall(segment, ..) => {
|
ExprKind::MethodCall(segment, ..) => {
|
||||||
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")
|
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")
|
||||||
|
|
|
@ -1104,6 +1104,7 @@ impl<'tcx> Debug for Rvalue<'tcx> {
|
||||||
NullOp::AlignOf => write!(fmt, "AlignOf({t})"),
|
NullOp::AlignOf => write!(fmt, "AlignOf({t})"),
|
||||||
NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"),
|
NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"),
|
||||||
NullOp::UbChecks => write!(fmt, "UbChecks()"),
|
NullOp::UbChecks => write!(fmt, "UbChecks()"),
|
||||||
|
NullOp::ContractChecks => write!(fmt, "ContractChecks()"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ThreadLocalRef(did) => ty::tls::with(|tcx| {
|
ThreadLocalRef(did) => ty::tls::with(|tcx| {
|
||||||
|
|
|
@ -1591,6 +1591,9 @@ pub enum NullOp<'tcx> {
|
||||||
/// Returns whether we should perform some UB-checking at runtime.
|
/// Returns whether we should perform some UB-checking at runtime.
|
||||||
/// See the `ub_checks` intrinsic docs for details.
|
/// See the `ub_checks` intrinsic docs for details.
|
||||||
UbChecks,
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
|
|
@ -230,7 +230,8 @@ impl<'tcx> Rvalue<'tcx> {
|
||||||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
|
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
|
||||||
tcx.types.usize
|
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 {
|
Rvalue::Aggregate(ref ak, ref ops) => match **ak {
|
||||||
AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64),
|
AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64),
|
||||||
AggregateKind::Tuple => {
|
AggregateKind::Tuple => {
|
||||||
|
|
|
@ -417,7 +417,11 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
|
||||||
| Rvalue::Discriminant(..)
|
| Rvalue::Discriminant(..)
|
||||||
| Rvalue::Len(..)
|
| Rvalue::Len(..)
|
||||||
| Rvalue::NullaryOp(
|
| Rvalue::NullaryOp(
|
||||||
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbChecks,
|
NullOp::SizeOf
|
||||||
|
| NullOp::AlignOf
|
||||||
|
| NullOp::OffsetOf(..)
|
||||||
|
| NullOp::UbChecks
|
||||||
|
| NullOp::ContractChecks,
|
||||||
_,
|
_,
|
||||||
) => {}
|
) => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -545,6 +545,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
|
||||||
.offset_of_subfield(self.typing_env(), layout, fields.iter())
|
.offset_of_subfield(self.typing_env(), layout, fields.iter())
|
||||||
.bytes(),
|
.bytes(),
|
||||||
NullOp::UbChecks => return None,
|
NullOp::UbChecks => return None,
|
||||||
|
NullOp::ContractChecks => return None,
|
||||||
};
|
};
|
||||||
let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
|
let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
|
||||||
let imm = ImmTy::from_uint(val, usize_layout);
|
let imm = ImmTy::from_uint(val, usize_layout);
|
||||||
|
|
|
@ -629,6 +629,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
||||||
.offset_of_subfield(self.typing_env, op_layout, fields.iter())
|
.offset_of_subfield(self.typing_env, op_layout, fields.iter())
|
||||||
.bytes(),
|
.bytes(),
|
||||||
NullOp::UbChecks => return None,
|
NullOp::UbChecks => return None,
|
||||||
|
NullOp::ContractChecks => return None,
|
||||||
};
|
};
|
||||||
ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into()
|
ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,17 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics {
|
||||||
});
|
});
|
||||||
terminator.kind = TerminatorKind::Goto { target };
|
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 => {
|
sym::forget => {
|
||||||
let target = target.unwrap();
|
let target = target.unwrap();
|
||||||
block.statements.push(Statement {
|
block.statements.push(Statement {
|
||||||
|
|
|
@ -457,6 +457,7 @@ impl<'tcx> Validator<'_, 'tcx> {
|
||||||
NullOp::AlignOf => {}
|
NullOp::AlignOf => {}
|
||||||
NullOp::OffsetOf(_) => {}
|
NullOp::OffsetOf(_) => {}
|
||||||
NullOp::UbChecks => {}
|
NullOp::UbChecks => {}
|
||||||
|
NullOp::ContractChecks => {}
|
||||||
},
|
},
|
||||||
|
|
||||||
Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),
|
Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),
|
||||||
|
|
|
@ -1379,7 +1379,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
||||||
Rvalue::Repeat(_, _)
|
Rvalue::Repeat(_, _)
|
||||||
| Rvalue::ThreadLocalRef(_)
|
| Rvalue::ThreadLocalRef(_)
|
||||||
| Rvalue::RawPtr(_, _)
|
| Rvalue::RawPtr(_, _)
|
||||||
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks, _)
|
| Rvalue::NullaryOp(
|
||||||
|
NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks | NullOp::ContractChecks,
|
||||||
|
_,
|
||||||
|
)
|
||||||
| Rvalue::Discriminant(_) => {}
|
| Rvalue::Discriminant(_) => {}
|
||||||
|
|
||||||
Rvalue::WrapUnsafeBinder(op, ty) => {
|
Rvalue::WrapUnsafeBinder(op, ty) => {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use rustc_ast::{
|
||||||
WhereClause, token,
|
WhereClause, token,
|
||||||
};
|
};
|
||||||
use rustc_errors::{Applicability, PResult};
|
use rustc_errors::{Applicability, PResult};
|
||||||
use rustc_span::{Ident, Span, kw};
|
use rustc_span::{Ident, Span, kw, sym};
|
||||||
use thin_vec::ThinVec;
|
use thin_vec::ThinVec;
|
||||||
|
|
||||||
use super::{ForceCollect, Parser, Trailing, UsePreAttrPos};
|
use super::{ForceCollect, Parser, Trailing, UsePreAttrPos};
|
||||||
|
@ -297,6 +297,42 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses an experimental fn contract
|
||||||
|
/// (`contract_requires(WWW) contract_ensures(ZZZ)`)
|
||||||
|
pub(super) fn parse_contract(
|
||||||
|
&mut self,
|
||||||
|
) -> PResult<'a, Option<rustc_ast::ptr::P<ast::FnContract>>> {
|
||||||
|
let gate = |span| {
|
||||||
|
if self.psess.contract_attribute_spans.contains(span) {
|
||||||
|
// span was generated via a builtin contracts attribute, so gate as end-user visible
|
||||||
|
self.psess.gated_spans.gate(sym::contracts, span);
|
||||||
|
} else {
|
||||||
|
// span was not generated via a builtin contracts attribute, so gate as internal machinery
|
||||||
|
self.psess.gated_spans.gate(sym::contracts_internals, span);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let requires = if self.eat_keyword_noexpect(exp!(ContractRequires).kw) {
|
||||||
|
let precond = self.parse_expr()?;
|
||||||
|
gate(precond.span);
|
||||||
|
Some(precond)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let ensures = if self.eat_keyword_noexpect(exp!(ContractEnsures).kw) {
|
||||||
|
let postcond = self.parse_expr()?;
|
||||||
|
gate(postcond.span);
|
||||||
|
Some(postcond)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if requires.is_none() && ensures.is_none() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(rustc_ast::ptr::P(ast::FnContract { requires, ensures })))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses an optional where-clause.
|
/// Parses an optional where-clause.
|
||||||
///
|
///
|
||||||
/// ```ignore (only-for-syntax-highlight)
|
/// ```ignore (only-for-syntax-highlight)
|
||||||
|
|
|
@ -213,9 +213,12 @@ impl<'a> Parser<'a> {
|
||||||
self.parse_use_item()?
|
self.parse_use_item()?
|
||||||
} else if self.check_fn_front_matter(check_pub, case) {
|
} else if self.check_fn_front_matter(check_pub, case) {
|
||||||
// FUNCTION ITEM
|
// FUNCTION ITEM
|
||||||
let (ident, sig, generics, body) =
|
let (ident, sig, generics, contract, body) =
|
||||||
self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?;
|
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)) {
|
} else if self.eat_keyword(exp!(Extern)) {
|
||||||
if self.eat_keyword(exp!(Crate)) {
|
if self.eat_keyword(exp!(Crate)) {
|
||||||
// EXTERN CRATE
|
// EXTERN CRATE
|
||||||
|
@ -2372,7 +2375,7 @@ impl<'a> Parser<'a> {
|
||||||
sig_lo: Span,
|
sig_lo: Span,
|
||||||
vis: &Visibility,
|
vis: &Visibility,
|
||||||
case: Case,
|
case: Case,
|
||||||
) -> PResult<'a, (Ident, FnSig, Generics, Option<P<Block>>)> {
|
) -> PResult<'a, (Ident, FnSig, Generics, Option<P<FnContract>>, Option<P<Block>>)> {
|
||||||
let fn_span = self.token.span;
|
let fn_span = self.token.span;
|
||||||
let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn`
|
let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn`
|
||||||
let ident = self.parse_ident()?; // `foo`
|
let ident = self.parse_ident()?; // `foo`
|
||||||
|
@ -2398,6 +2401,8 @@ impl<'a> Parser<'a> {
|
||||||
// inside `parse_fn_body()`.
|
// inside `parse_fn_body()`.
|
||||||
let fn_params_end = self.prev_token.span.shrink_to_hi();
|
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`
|
generics.where_clause = self.parse_where_clause()?; // `where T: Ord`
|
||||||
|
|
||||||
// `fn_params_end` is needed only when it's followed by a where clause.
|
// `fn_params_end` is needed only when it's followed by a where clause.
|
||||||
|
@ -2409,7 +2414,7 @@ impl<'a> Parser<'a> {
|
||||||
let body =
|
let body =
|
||||||
self.parse_fn_body(attrs, &ident, &mut sig_hi, fn_parse_mode.req_body, fn_params_end)?;
|
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);
|
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
|
/// Provide diagnostics when function body is not found
|
||||||
|
|
|
@ -83,6 +83,8 @@ pub enum TokenType {
|
||||||
KwCatch,
|
KwCatch,
|
||||||
KwConst,
|
KwConst,
|
||||||
KwContinue,
|
KwContinue,
|
||||||
|
KwContractEnsures,
|
||||||
|
KwContractRequires,
|
||||||
KwCrate,
|
KwCrate,
|
||||||
KwDefault,
|
KwDefault,
|
||||||
KwDyn,
|
KwDyn,
|
||||||
|
@ -217,6 +219,8 @@ impl TokenType {
|
||||||
KwCatch,
|
KwCatch,
|
||||||
KwConst,
|
KwConst,
|
||||||
KwContinue,
|
KwContinue,
|
||||||
|
KwContractEnsures,
|
||||||
|
KwContractRequires,
|
||||||
KwCrate,
|
KwCrate,
|
||||||
KwDefault,
|
KwDefault,
|
||||||
KwDyn,
|
KwDyn,
|
||||||
|
@ -289,6 +293,8 @@ impl TokenType {
|
||||||
TokenType::KwCatch => Some(kw::Catch),
|
TokenType::KwCatch => Some(kw::Catch),
|
||||||
TokenType::KwConst => Some(kw::Const),
|
TokenType::KwConst => Some(kw::Const),
|
||||||
TokenType::KwContinue => Some(kw::Continue),
|
TokenType::KwContinue => Some(kw::Continue),
|
||||||
|
TokenType::KwContractEnsures => Some(kw::ContractEnsures),
|
||||||
|
TokenType::KwContractRequires => Some(kw::ContractRequires),
|
||||||
TokenType::KwCrate => Some(kw::Crate),
|
TokenType::KwCrate => Some(kw::Crate),
|
||||||
TokenType::KwDefault => Some(kw::Default),
|
TokenType::KwDefault => Some(kw::Default),
|
||||||
TokenType::KwDyn => Some(kw::Dyn),
|
TokenType::KwDyn => Some(kw::Dyn),
|
||||||
|
@ -519,6 +525,8 @@ macro_rules! exp {
|
||||||
(Catch) => { exp!(@kw, Catch, KwCatch) };
|
(Catch) => { exp!(@kw, Catch, KwCatch) };
|
||||||
(Const) => { exp!(@kw, Const, KwConst) };
|
(Const) => { exp!(@kw, Const, KwConst) };
|
||||||
(Continue) => { exp!(@kw, Continue, KwContinue) };
|
(Continue) => { exp!(@kw, Continue, KwContinue) };
|
||||||
|
(ContractEnsures) => { exp!(@kw, ContractEnsures, KwContractEnsures) };
|
||||||
|
(ContractRequires) => { exp!(@kw, ContractRequires, KwContractRequires) };
|
||||||
(Crate) => { exp!(@kw, Crate, KwCrate) };
|
(Crate) => { exp!(@kw, Crate, KwCrate) };
|
||||||
(Default) => { exp!(@kw, Default, KwDefault) };
|
(Default) => { exp!(@kw, Default, KwDefault) };
|
||||||
(Dyn) => { exp!(@kw, Dyn, KwDyn) };
|
(Dyn) => { exp!(@kw, Dyn, KwDyn) };
|
||||||
|
|
|
@ -174,10 +174,13 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> {
|
||||||
_ctxt,
|
_ctxt,
|
||||||
_ident,
|
_ident,
|
||||||
_vis,
|
_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 => {
|
) if let Some(coroutine_kind) = header.coroutine_kind => {
|
||||||
self.visit_fn_header(header);
|
self.visit_fn_header(header);
|
||||||
self.visit_generics(generics);
|
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
|
// For async functions, we need to create their inner defs inside of a
|
||||||
// closure to match their desugared representation. Besides that,
|
// closure to match their desugared representation. Besides that,
|
||||||
|
|
|
@ -1019,7 +1019,7 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
|
||||||
// Create a label rib for the function.
|
// Create a label rib for the function.
|
||||||
this.with_label_rib(RibKind::FnOrCoroutine, |this| {
|
this.with_label_rib(RibKind::FnOrCoroutine, |this| {
|
||||||
match fn_kind {
|
match fn_kind {
|
||||||
FnKind::Fn(_, _, _, Fn { sig, generics, body, .. }) => {
|
FnKind::Fn(_, _, _, Fn { sig, generics, contract, body, .. }) => {
|
||||||
this.visit_generics(generics);
|
this.visit_generics(generics);
|
||||||
|
|
||||||
let declaration = &sig.decl;
|
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 {
|
if let Some(body) = body {
|
||||||
// Ignore errors in function bodies if this is rustdoc
|
// Ignore errors in function bodies if this is rustdoc
|
||||||
// Be sure not to set this until the function signature has been resolved.
|
// Be sure not to set this until the function signature has been resolved.
|
||||||
|
|
|
@ -119,6 +119,7 @@ pub(crate) fn disallow_cfgs(sess: &Session, user_cfgs: &Cfg) {
|
||||||
(sym::overflow_checks, None) => disallow(cfg, "-C overflow-checks"),
|
(sym::overflow_checks, None) => disallow(cfg, "-C overflow-checks"),
|
||||||
(sym::debug_assertions, None) => disallow(cfg, "-C debug-assertions"),
|
(sym::debug_assertions, None) => disallow(cfg, "-C debug-assertions"),
|
||||||
(sym::ub_checks, None) => disallow(cfg, "-Z ub-checks"),
|
(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::sanitize, None | Some(_)) => disallow(cfg, "-Z sanitizer"),
|
||||||
(
|
(
|
||||||
sym::sanitizer_cfi_generalize_pointers | sym::sanitizer_cfi_normalize_integers,
|
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 {
|
if sess.is_nightly_build() && sess.opts.unstable_opts.emscripten_wasm_eh {
|
||||||
ins_none!(sym::emscripten_wasm_eh);
|
ins_none!(sym::emscripten_wasm_eh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sess.contract_checks() {
|
||||||
|
ins_none!(sym::contract_checks);
|
||||||
|
}
|
||||||
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,6 +470,7 @@ impl CheckCfg {
|
||||||
ins!(sym::target_thread_local, no_values);
|
ins!(sym::target_thread_local, no_values);
|
||||||
|
|
||||||
ins!(sym::ub_checks, no_values);
|
ins!(sym::ub_checks, no_values);
|
||||||
|
ins!(sym::contract_checks, no_values);
|
||||||
|
|
||||||
ins!(sym::unix, no_values);
|
ins!(sym::unix, no_values);
|
||||||
ins!(sym::windows, no_values);
|
ins!(sym::windows, no_values);
|
||||||
|
|
|
@ -2114,6 +2114,8 @@ options! {
|
||||||
"the backend to use"),
|
"the backend to use"),
|
||||||
combine_cgu: bool = (false, parse_bool, [TRACKED],
|
combine_cgu: bool = (false, parse_bool, [TRACKED],
|
||||||
"combine CGUs into a single one"),
|
"combine CGUs into a single one"),
|
||||||
|
contract_checks: Option<bool> = (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],
|
coverage_options: CoverageOptions = (CoverageOptions::default(), parse_coverage_options, [TRACKED],
|
||||||
"control details of coverage instrumentation"),
|
"control details of coverage instrumentation"),
|
||||||
crate_attr: Vec<String> = (Vec::new(), parse_string_push, [TRACKED],
|
crate_attr: Vec<String> = (Vec::new(), parse_string_push, [TRACKED],
|
||||||
|
|
|
@ -207,6 +207,10 @@ pub struct ParseSess {
|
||||||
pub config: Cfg,
|
pub config: Cfg,
|
||||||
pub check_config: CheckCfg,
|
pub check_config: CheckCfg,
|
||||||
pub edition: Edition,
|
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<Span>,
|
||||||
/// Places where raw identifiers were used. This is used to avoid complaining about idents
|
/// Places where raw identifiers were used. This is used to avoid complaining about idents
|
||||||
/// clashing with keywords in new editions.
|
/// clashing with keywords in new editions.
|
||||||
pub raw_identifier_spans: AppendOnlyVec<Span>,
|
pub raw_identifier_spans: AppendOnlyVec<Span>,
|
||||||
|
@ -255,6 +259,7 @@ impl ParseSess {
|
||||||
config: Cfg::default(),
|
config: Cfg::default(),
|
||||||
check_config: CheckCfg::default(),
|
check_config: CheckCfg::default(),
|
||||||
edition: ExpnId::root().expn_data().edition,
|
edition: ExpnId::root().expn_data().edition,
|
||||||
|
contract_attribute_spans: Default::default(),
|
||||||
raw_identifier_spans: Default::default(),
|
raw_identifier_spans: Default::default(),
|
||||||
bad_unicode_identifiers: Lock::new(Default::default()),
|
bad_unicode_identifiers: Lock::new(Default::default()),
|
||||||
source_map,
|
source_map,
|
||||||
|
|
|
@ -709,6 +709,10 @@ impl Session {
|
||||||
self.opts.unstable_opts.ub_checks.unwrap_or(self.opts.debug_assertions)
|
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 {
|
pub fn relocation_model(&self) -> RelocModel {
|
||||||
self.opts.cg.relocation_model.unwrap_or(self.target.relocation_model)
|
self.opts.cg.relocation_model.unwrap_or(self.target.relocation_model)
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,6 +291,7 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> {
|
||||||
indices.iter().map(|idx| idx.stable(tables)).collect(),
|
indices.iter().map(|idx| idx.stable(tables)).collect(),
|
||||||
),
|
),
|
||||||
UbChecks => stable_mir::mir::NullOp::UbChecks,
|
UbChecks => stable_mir::mir::NullOp::UbChecks,
|
||||||
|
ContractChecks => stable_mir::mir::NullOp::ContractChecks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1163,6 +1163,8 @@ pub enum DesugaringKind {
|
||||||
WhileLoop,
|
WhileLoop,
|
||||||
/// `async Fn()` bound modifier
|
/// `async Fn()` bound modifier
|
||||||
BoundModifier,
|
BoundModifier,
|
||||||
|
/// Calls to contract checks (`#[requires]` to precond, `#[ensures]` to postcond)
|
||||||
|
Contract,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DesugaringKind {
|
impl DesugaringKind {
|
||||||
|
@ -1179,6 +1181,7 @@ impl DesugaringKind {
|
||||||
DesugaringKind::ForLoop => "`for` loop",
|
DesugaringKind::ForLoop => "`for` loop",
|
||||||
DesugaringKind::WhileLoop => "`while` loop",
|
DesugaringKind::WhileLoop => "`while` loop",
|
||||||
DesugaringKind::BoundModifier => "trait bound modifier",
|
DesugaringKind::BoundModifier => "trait bound modifier",
|
||||||
|
DesugaringKind::Contract => "contract check",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,8 @@ symbols! {
|
||||||
MacroRules: "macro_rules",
|
MacroRules: "macro_rules",
|
||||||
Raw: "raw",
|
Raw: "raw",
|
||||||
Reuse: "reuse",
|
Reuse: "reuse",
|
||||||
|
ContractEnsures: "contract_ensures",
|
||||||
|
ContractRequires: "contract_requires",
|
||||||
Safe: "safe",
|
Safe: "safe",
|
||||||
Union: "union",
|
Union: "union",
|
||||||
Yeet: "yeet",
|
Yeet: "yeet",
|
||||||
|
@ -569,6 +571,7 @@ symbols! {
|
||||||
cfg_attr,
|
cfg_attr,
|
||||||
cfg_attr_multi,
|
cfg_attr_multi,
|
||||||
cfg_boolean_literals,
|
cfg_boolean_literals,
|
||||||
|
cfg_contract_checks,
|
||||||
cfg_doctest,
|
cfg_doctest,
|
||||||
cfg_emscripten_wasm_eh,
|
cfg_emscripten_wasm_eh,
|
||||||
cfg_eval,
|
cfg_eval,
|
||||||
|
@ -678,6 +681,14 @@ symbols! {
|
||||||
const_ty_placeholder: "<const_ty>",
|
const_ty_placeholder: "<const_ty>",
|
||||||
constant,
|
constant,
|
||||||
constructor,
|
constructor,
|
||||||
|
contract_build_check_ensures,
|
||||||
|
contract_check_ensures,
|
||||||
|
contract_check_requires,
|
||||||
|
contract_checks,
|
||||||
|
contracts,
|
||||||
|
contracts_ensures,
|
||||||
|
contracts_internals,
|
||||||
|
contracts_requires,
|
||||||
convert_identity,
|
convert_identity,
|
||||||
copy,
|
copy,
|
||||||
copy_closures,
|
copy_closures,
|
||||||
|
|
|
@ -608,7 +608,8 @@ impl Rvalue {
|
||||||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
|
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
|
||||||
Ok(Ty::usize_ty())
|
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 {
|
Rvalue::Aggregate(ak, ops) => match *ak {
|
||||||
AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64),
|
AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64),
|
||||||
AggregateKind::Tuple => Ok(Ty::new_tuple(
|
AggregateKind::Tuple => Ok(Ty::new_tuple(
|
||||||
|
@ -1007,6 +1008,8 @@ pub enum NullOp {
|
||||||
OffsetOf(Vec<(VariantIdx, FieldIdx)>),
|
OffsetOf(Vec<(VariantIdx, FieldIdx)>),
|
||||||
/// cfg!(ub_checks), but at codegen time
|
/// cfg!(ub_checks), but at codegen time
|
||||||
UbChecks,
|
UbChecks,
|
||||||
|
/// cfg!(contract_checks), but at codegen time
|
||||||
|
ContractChecks,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Operand {
|
impl Operand {
|
||||||
|
|
21
library/core/src/contracts.rs
Normal file
21
library/core/src/contracts.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//! Unstable module containing the unstable contracts lang items and attribute macros.
|
||||||
|
#![cfg(not(bootstrap))]
|
||||||
|
|
||||||
|
pub use crate::macros::builtin::{contracts_ensures as ensures, contracts_requires as requires};
|
||||||
|
|
||||||
|
/// Emitted by rustc as a desugaring of `#[ensures(PRED)] fn foo() -> R { ... [return R;] ... }`
|
||||||
|
/// into: `fn foo() { let _check = build_check_ensures(|ret| PRED) ... [return _check(R);] ... }`
|
||||||
|
/// (including the implicit return of the tail expression, if any).
|
||||||
|
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||||
|
#[lang = "contract_build_check_ensures"]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn build_check_ensures<Ret, C>(cond: C) -> impl (Fn(Ret) -> Ret) + Copy
|
||||||
|
where
|
||||||
|
C: for<'a> Fn(&'a Ret) -> bool + Copy + 'static,
|
||||||
|
{
|
||||||
|
#[track_caller]
|
||||||
|
move |ret| {
|
||||||
|
crate::intrinsics::contract_check_ensures(&ret, cond);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
|
@ -4064,6 +4064,52 @@ pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize)
|
||||||
// Runtime NOP
|
// Runtime NOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether we should perform contract-checking at runtime.
|
||||||
|
///
|
||||||
|
/// This is meant to be similar to the ub_checks intrinsic, in terms
|
||||||
|
/// of not prematurely commiting at compile-time to whether contract
|
||||||
|
/// checking is turned on, so that we can specify contracts in libstd
|
||||||
|
/// and let an end user opt into turning them on.
|
||||||
|
#[cfg(not(bootstrap))]
|
||||||
|
#[rustc_const_unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||||
|
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||||
|
#[inline(always)]
|
||||||
|
#[rustc_intrinsic]
|
||||||
|
pub const fn contract_checks() -> bool {
|
||||||
|
// FIXME: should this be `false` or `cfg!(contract_checks)`?
|
||||||
|
|
||||||
|
// cfg!(contract_checks)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the pre-condition `cond` has been met.
|
||||||
|
///
|
||||||
|
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
|
||||||
|
/// returns false.
|
||||||
|
#[cfg(not(bootstrap))]
|
||||||
|
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||||
|
#[lang = "contract_check_requires"]
|
||||||
|
#[rustc_intrinsic]
|
||||||
|
pub fn contract_check_requires<C: Fn() -> bool>(cond: C) {
|
||||||
|
if contract_checks() && !cond() {
|
||||||
|
// Emit no unwind panic in case this was a safety requirement.
|
||||||
|
crate::panicking::panic_nounwind("failed requires check");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the post-condition `cond` has been met.
|
||||||
|
///
|
||||||
|
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
|
||||||
|
/// returns false.
|
||||||
|
#[cfg(not(bootstrap))]
|
||||||
|
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||||
|
#[rustc_intrinsic]
|
||||||
|
pub fn contract_check_ensures<'a, Ret, C: Fn(&'a Ret) -> bool>(ret: &'a Ret, cond: C) {
|
||||||
|
if contract_checks() && !cond(ret) {
|
||||||
|
crate::panicking::panic_nounwind("failed ensures check");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The intrinsic will return the size stored in that vtable.
|
/// The intrinsic will return the size stored in that vtable.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
|
|
@ -113,6 +113,7 @@
|
||||||
#![feature(bigint_helper_methods)]
|
#![feature(bigint_helper_methods)]
|
||||||
#![feature(bstr)]
|
#![feature(bstr)]
|
||||||
#![feature(bstr_internals)]
|
#![feature(bstr_internals)]
|
||||||
|
#![feature(closure_track_caller)]
|
||||||
#![feature(const_carrying_mul_add)]
|
#![feature(const_carrying_mul_add)]
|
||||||
#![feature(const_eval_select)]
|
#![feature(const_eval_select)]
|
||||||
#![feature(core_intrinsics)]
|
#![feature(core_intrinsics)]
|
||||||
|
@ -247,6 +248,10 @@ pub mod autodiff {
|
||||||
pub use crate::macros::builtin::autodiff;
|
pub use crate::macros::builtin::autodiff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(bootstrap))]
|
||||||
|
#[unstable(feature = "contracts", issue = "128044")]
|
||||||
|
pub mod contracts;
|
||||||
|
|
||||||
#[unstable(feature = "cfg_match", issue = "115585")]
|
#[unstable(feature = "cfg_match", issue = "115585")]
|
||||||
pub use crate::macros::cfg_match;
|
pub use crate::macros::cfg_match;
|
||||||
|
|
||||||
|
|
|
@ -1777,6 +1777,32 @@ pub(crate) mod builtin {
|
||||||
/* compiler built-in */
|
/* compiler built-in */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attribute macro applied to a function to give it a post-condition.
|
||||||
|
///
|
||||||
|
/// The attribute carries an argument token-tree which is
|
||||||
|
/// eventually parsed as a unary closure expression that is
|
||||||
|
/// invoked on a reference to the return value.
|
||||||
|
#[cfg(not(bootstrap))]
|
||||||
|
#[unstable(feature = "contracts", issue = "128044")]
|
||||||
|
#[allow_internal_unstable(contracts_internals)]
|
||||||
|
#[rustc_builtin_macro]
|
||||||
|
pub macro contracts_ensures($item:item) {
|
||||||
|
/* compiler built-in */
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attribute macro applied to a function to give it a precondition.
|
||||||
|
///
|
||||||
|
/// The attribute carries an argument token-tree which is
|
||||||
|
/// eventually parsed as an boolean expression with access to the
|
||||||
|
/// function's formal parameters
|
||||||
|
#[cfg(not(bootstrap))]
|
||||||
|
#[unstable(feature = "contracts", issue = "128044")]
|
||||||
|
#[allow_internal_unstable(contracts_internals)]
|
||||||
|
#[rustc_builtin_macro]
|
||||||
|
pub macro contracts_requires($item:item) {
|
||||||
|
/* compiler built-in */
|
||||||
|
}
|
||||||
|
|
||||||
/// Attribute macro applied to a function to register it as a handler for allocation failure.
|
/// 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).
|
/// See also [`std::alloc::handle_alloc_error`](../../../std/alloc/fn.handle_alloc_error.html).
|
||||||
|
|
|
@ -362,18 +362,21 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
|
||||||
defaultness: ld,
|
defaultness: ld,
|
||||||
sig: lf,
|
sig: lf,
|
||||||
generics: lg,
|
generics: lg,
|
||||||
|
contract: lc,
|
||||||
body: lb,
|
body: lb,
|
||||||
}),
|
}),
|
||||||
Fn(box ast::Fn {
|
Fn(box ast::Fn {
|
||||||
defaultness: rd,
|
defaultness: rd,
|
||||||
sig: rf,
|
sig: rf,
|
||||||
generics: rg,
|
generics: rg,
|
||||||
|
contract: rc,
|
||||||
body: rb,
|
body: rb,
|
||||||
}),
|
}),
|
||||||
) => {
|
) => {
|
||||||
eq_defaultness(*ld, *rd)
|
eq_defaultness(*ld, *rd)
|
||||||
&& eq_fn_sig(lf, rf)
|
&& eq_fn_sig(lf, rf)
|
||||||
&& eq_generics(lg, rg)
|
&& eq_generics(lg, rg)
|
||||||
|
&& eq_opt_fn_contract(lc, rc)
|
||||||
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
||||||
},
|
},
|
||||||
(Mod(lu, lmk), Mod(ru, rmk)) => {
|
(Mod(lu, lmk), Mod(ru, rmk)) => {
|
||||||
|
@ -497,18 +500,21 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
|
||||||
defaultness: ld,
|
defaultness: ld,
|
||||||
sig: lf,
|
sig: lf,
|
||||||
generics: lg,
|
generics: lg,
|
||||||
|
contract: lc,
|
||||||
body: lb,
|
body: lb,
|
||||||
}),
|
}),
|
||||||
Fn(box ast::Fn {
|
Fn(box ast::Fn {
|
||||||
defaultness: rd,
|
defaultness: rd,
|
||||||
sig: rf,
|
sig: rf,
|
||||||
generics: rg,
|
generics: rg,
|
||||||
|
contract: rc,
|
||||||
body: rb,
|
body: rb,
|
||||||
}),
|
}),
|
||||||
) => {
|
) => {
|
||||||
eq_defaultness(*ld, *rd)
|
eq_defaultness(*ld, *rd)
|
||||||
&& eq_fn_sig(lf, rf)
|
&& eq_fn_sig(lf, rf)
|
||||||
&& eq_generics(lg, rg)
|
&& eq_generics(lg, rg)
|
||||||
|
&& eq_opt_fn_contract(lc, rc)
|
||||||
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
&& 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,
|
defaultness: ld,
|
||||||
sig: lf,
|
sig: lf,
|
||||||
generics: lg,
|
generics: lg,
|
||||||
|
contract: lc,
|
||||||
body: lb,
|
body: lb,
|
||||||
}),
|
}),
|
||||||
Fn(box ast::Fn {
|
Fn(box ast::Fn {
|
||||||
defaultness: rd,
|
defaultness: rd,
|
||||||
sig: rf,
|
sig: rf,
|
||||||
generics: rg,
|
generics: rg,
|
||||||
|
contract: rc,
|
||||||
body: rb,
|
body: rb,
|
||||||
}),
|
}),
|
||||||
) => {
|
) => {
|
||||||
eq_defaultness(*ld, *rd)
|
eq_defaultness(*ld, *rd)
|
||||||
&& eq_fn_sig(lf, rf)
|
&& eq_fn_sig(lf, rf)
|
||||||
&& eq_generics(lg, rg)
|
&& eq_generics(lg, rg)
|
||||||
|
&& eq_opt_fn_contract(lc, rc)
|
||||||
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
&& 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)
|
&& eq_ext(&l.ext, &r.ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn eq_opt_fn_contract(l: &Option<P<FnContract>>, r: &Option<P<FnContract>>) -> 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 {
|
pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
|
||||||
over(&l.params, &r.params, eq_generic_param)
|
over(&l.params, &r.params, eq_generic_param)
|
||||||
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
|
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
|
||||||
|
|
|
@ -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::ShallowInitBox(_, _) => Ok(()),
|
||||||
Rvalue::UnaryOp(_, operand) => {
|
Rvalue::UnaryOp(_, operand) => {
|
||||||
let ty = operand.ty(body, tcx);
|
let ty = operand.ty(body, tcx);
|
||||||
|
|
|
@ -1150,6 +1150,11 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
||||||
interp_ok(ecx.tcx.sess.ub_checks())
|
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)]
|
#[inline(always)]
|
||||||
fn thread_local_static_pointer(
|
fn thread_local_static_pointer(
|
||||||
ecx: &mut MiriInterpCx<'tcx>,
|
ecx: &mut MiriInterpCx<'tcx>,
|
||||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `has_foo`
|
||||||
LL | #[cfg(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: consider using a Cargo feature instead
|
||||||
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
|
|
|
@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable`
|
||||||
LL | #[cfg(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: consider using a Cargo feature instead
|
||||||
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
|
|
|
@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable`
|
||||||
LL | #[cfg(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: consider using a Cargo feature instead
|
||||||
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `value`
|
||||||
LL | #[cfg(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)`
|
= help: to expect this configuration use `--check-cfg=cfg(value)`
|
||||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_value`
|
||||||
LL | #[cfg(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)`
|
= help: to expect this configuration use `--check-cfg=cfg(my_value)`
|
||||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
|
||||||
LL | #[cfg(unknown_key = "value")]
|
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"))`
|
= help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))`
|
||||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
|
||||||
LL | #[cfg(unknown_key = "value")]
|
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"))`
|
= help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))`
|
||||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||||
|
|
|
@ -44,7 +44,7 @@ warning: unexpected `cfg` condition name: `uu`
|
||||||
LL | #[cfg_attr(uu, unix)]
|
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)`
|
= help: to expect this configuration use `--check-cfg=cfg(uu)`
|
||||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ warning: unexpected `cfg` condition name: `r#false`
|
||||||
LL | #[cfg(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)`
|
= help: to expect this configuration use `--check-cfg=cfg(r#false)`
|
||||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ warning: unexpected `cfg` condition name: `r#false`
|
||||||
LL | #[cfg(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)`
|
= help: to expect this configuration use `--check-cfg=cfg(r#false)`
|
||||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_lib_cfg`
|
||||||
LL | cfg_macro::my_lib_macro!();
|
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
|
= 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: 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`
|
= 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`
|
||||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_lib_cfg`
|
||||||
LL | cfg_macro::my_lib_macro!();
|
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
|
= 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: 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)`
|
= help: to expect this configuration use `--check-cfg=cfg(my_lib_cfg)`
|
||||||
|
|
|
@ -5,6 +5,7 @@ LL | #[cfg(list_all_well_known_cfgs)]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: expected names are: `clippy`
|
= help: expected names are: `clippy`
|
||||||
|
`contract_checks`
|
||||||
`debug_assertions`
|
`debug_assertions`
|
||||||
`doc`
|
`doc`
|
||||||
`doctest`
|
`doctest`
|
||||||
|
|
28
tests/ui/contracts/contract-annotation-limitations.rs
Normal file
28
tests/ui/contracts/contract-annotation-limitations.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
//! Test for some of the existing limitations and the current error messages.
|
||||||
|
//! Some of these limitations may be removed in the future.
|
||||||
|
|
||||||
|
#![feature(contracts)]
|
||||||
|
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
/// Represent a 5-star system.
|
||||||
|
struct Stars(u8);
|
||||||
|
|
||||||
|
impl Stars {
|
||||||
|
fn is_valid(&self) -> bool {
|
||||||
|
self.0 <= 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ParseStars {
|
||||||
|
#[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
|
||||||
|
//~^ ERROR contract annotations is only supported in functions with bodies
|
||||||
|
fn parse_string(input: String) -> Option<Stars>;
|
||||||
|
|
||||||
|
#[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
|
||||||
|
//~^ ERROR contract annotations is only supported in functions with bodies
|
||||||
|
fn parse<T>(input: T) -> Option<Stars> where T: for<'a> Into<&'a str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
}
|
23
tests/ui/contracts/contract-annotation-limitations.stderr
Normal file
23
tests/ui/contracts/contract-annotation-limitations.stderr
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
error: contract annotations is only supported in functions with bodies
|
||||||
|
--> $DIR/contract-annotation-limitations.rs:18:5
|
||||||
|
|
|
||||||
|
LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: contract annotations is only supported in functions with bodies
|
||||||
|
--> $DIR/contract-annotation-limitations.rs:22:5
|
||||||
|
|
|
||||||
|
LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||||
|
--> $DIR/contract-annotation-limitations.rs:4:12
|
||||||
|
|
|
||||||
|
LL | #![feature(contracts)]
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors; 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
71
tests/ui/contracts/contract-attributes-generics.rs
Normal file
71
tests/ui/contracts/contract-attributes-generics.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
//! Test that contracts can be applied to generic functions.
|
||||||
|
|
||||||
|
//@ revisions: unchk_pass chk_pass chk_fail_pre chk_fail_post chk_const_fail
|
||||||
|
//
|
||||||
|
//@ [unchk_pass] run-pass
|
||||||
|
//@ [chk_pass] run-pass
|
||||||
|
//
|
||||||
|
//@ [chk_fail_pre] run-fail
|
||||||
|
//@ [chk_fail_post] run-fail
|
||||||
|
//@ [chk_const_fail] run-fail
|
||||||
|
//
|
||||||
|
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||||
|
//
|
||||||
|
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_const_fail] compile-flags: -Zcontract-checks=yes
|
||||||
|
|
||||||
|
#![feature(contracts)]
|
||||||
|
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||||
|
|
||||||
|
use std::ops::Sub;
|
||||||
|
|
||||||
|
/// Dummy fn contract that precondition fails for val < 0, and post-condition fail for val == 1
|
||||||
|
#[core::contracts::requires(val > 0u8.into())]
|
||||||
|
#[core::contracts::ensures(|ret| *ret > 0u8.into())]
|
||||||
|
fn decrement<T>(val: T) -> T
|
||||||
|
where T: PartialOrd + Sub<Output=T> + From<u8>
|
||||||
|
{
|
||||||
|
val - 1u8.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a structure that takes a constant parameter.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct Capped<const MAX: usize>(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);
|
||||||
|
}
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
11
tests/ui/contracts/contract-attributes-nest.chk_pass.stderr
Normal file
11
tests/ui/contracts/contract-attributes-nest.chk_pass.stderr
Normal file
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
45
tests/ui/contracts/contract-attributes-nest.rs
Normal file
45
tests/ui/contracts/contract-attributes-nest.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post
|
||||||
|
//
|
||||||
|
//@ [unchk_pass] run-pass
|
||||||
|
//@ [unchk_fail_pre] run-pass
|
||||||
|
//@ [unchk_fail_post] run-pass
|
||||||
|
//@ [chk_pass] run-pass
|
||||||
|
//
|
||||||
|
//@ [chk_fail_pre] run-fail
|
||||||
|
//@ [chk_fail_post] run-fail
|
||||||
|
//
|
||||||
|
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||||
|
//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no
|
||||||
|
//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no
|
||||||
|
//
|
||||||
|
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes
|
||||||
|
|
||||||
|
#![feature(contracts)]
|
||||||
|
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||||
|
|
||||||
|
#[core::contracts::requires(x.baz > 0)]
|
||||||
|
#[core::contracts::ensures(|ret| *ret > 100)]
|
||||||
|
fn nest(x: Baz) -> i32
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
return x.baz + 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Baz { baz: i32 }
|
||||||
|
|
||||||
|
const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 };
|
||||||
|
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||||
|
const BAZ_FAIL_POST: Baz = Baz { baz: 10 };
|
||||||
|
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||||
|
const BAZ_FAIL_PRE: Baz = Baz { baz: -10 };
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_eq!(nest(BAZ_PASS_PRE_POST), 150);
|
||||||
|
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||||
|
nest(BAZ_FAIL_PRE);
|
||||||
|
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||||
|
nest(BAZ_FAIL_POST);
|
||||||
|
}
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
11
tests/ui/contracts/contract-attributes-tail.chk_pass.stderr
Normal file
11
tests/ui/contracts/contract-attributes-tail.chk_pass.stderr
Normal file
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
43
tests/ui/contracts/contract-attributes-tail.rs
Normal file
43
tests/ui/contracts/contract-attributes-tail.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post
|
||||||
|
//
|
||||||
|
//@ [unchk_pass] run-pass
|
||||||
|
//@ [unchk_fail_pre] run-pass
|
||||||
|
//@ [unchk_fail_post] run-pass
|
||||||
|
//@ [chk_pass] run-pass
|
||||||
|
//
|
||||||
|
//@ [chk_fail_pre] run-fail
|
||||||
|
//@ [chk_fail_post] run-fail
|
||||||
|
//
|
||||||
|
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||||
|
//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no
|
||||||
|
//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no
|
||||||
|
//
|
||||||
|
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes
|
||||||
|
|
||||||
|
#![feature(contracts)]
|
||||||
|
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||||
|
|
||||||
|
#[core::contracts::requires(x.baz > 0)]
|
||||||
|
#[core::contracts::ensures(|ret| *ret > 100)]
|
||||||
|
fn tail(x: Baz) -> i32
|
||||||
|
{
|
||||||
|
x.baz + 50
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Baz { baz: i32 }
|
||||||
|
|
||||||
|
const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 };
|
||||||
|
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||||
|
const BAZ_FAIL_POST: Baz = Baz { baz: 10 };
|
||||||
|
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||||
|
const BAZ_FAIL_PRE: Baz = Baz { baz: -10 };
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_eq!(tail(BAZ_PASS_PRE_POST), 150);
|
||||||
|
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||||
|
tail(BAZ_FAIL_PRE);
|
||||||
|
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||||
|
tail(BAZ_FAIL_POST);
|
||||||
|
}
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
26
tests/ui/contracts/contract-captures-via-closure-copy.rs
Normal file
26
tests/ui/contracts/contract-captures-via-closure-copy.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
//@ run-fail
|
||||||
|
//@ compile-flags: -Zcontract-checks=yes
|
||||||
|
|
||||||
|
#![feature(contracts)]
|
||||||
|
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||||
|
|
||||||
|
struct Baz {
|
||||||
|
baz: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
#[core::contracts::requires(x.baz > 0)]
|
||||||
|
#[core::contracts::ensures({let old = x.baz; move |ret:&Baz| ret.baz == old*2 })]
|
||||||
|
// Relevant thing is this: ^^^^^^^^^^^^^^^
|
||||||
|
// because we are capturing state that is Copy
|
||||||
|
fn doubler(x: Baz) -> Baz {
|
||||||
|
Baz { baz: x.baz + 10 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_eq!(doubler(Baz { baz: 10 }).baz, 20);
|
||||||
|
assert_eq!(doubler(Baz { baz: 100 }).baz, 200);
|
||||||
|
// This is a *run-fail* test because it is still exercising the
|
||||||
|
// contract machinery, specifically because this second invocation
|
||||||
|
// of `doubler` shows how the code does not meet its contract.
|
||||||
|
}
|
11
tests/ui/contracts/contract-captures-via-closure-copy.stderr
Normal file
11
tests/ui/contracts/contract-captures-via-closure-copy.stderr
Normal file
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
23
tests/ui/contracts/contract-captures-via-closure-noncopy.rs
Normal file
23
tests/ui/contracts/contract-captures-via-closure-noncopy.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//@ compile-flags: -Zcontract-checks=yes
|
||||||
|
|
||||||
|
#![feature(contracts)]
|
||||||
|
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||||
|
|
||||||
|
struct Baz {
|
||||||
|
baz: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
#[core::contracts::requires(x.baz > 0)]
|
||||||
|
#[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })]
|
||||||
|
// Relevant thing is this: ^^^^^^^^^^^
|
||||||
|
// because we are capturing state that is non-Copy.
|
||||||
|
//~^^^ ERROR trait bound `Baz: std::marker::Copy` is not satisfied
|
||||||
|
fn doubler(x: Baz) -> Baz {
|
||||||
|
Baz { baz: x.baz + 10 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_eq!(doubler(Baz { baz: 10 }).baz, 20);
|
||||||
|
assert_eq!(doubler(Baz { baz: 100 }).baz, 200);
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||||
|
--> $DIR/contract-captures-via-closure-noncopy.rs:3:12
|
||||||
|
|
|
||||||
|
LL | #![feature(contracts)]
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `Baz: std::marker::Copy` is not satisfied in `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12:57}`
|
||||||
|
--> $DIR/contract-captures-via-closure-noncopy.rs:12:1
|
||||||
|
|
|
||||||
|
LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------------------------------------^^^^
|
||||||
|
| | |
|
||||||
|
| | within this `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12:57}`
|
||||||
|
| | this tail expression is of type `{closure@contract-captures-via-closure-noncopy.rs:12:42}`
|
||||||
|
| unsatisfied trait bound
|
||||||
|
|
|
||||||
|
= help: within `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12:57}`, the trait `std::marker::Copy` is not implemented for `Baz`
|
||||||
|
note: required because it's used within this closure
|
||||||
|
--> $DIR/contract-captures-via-closure-noncopy.rs:12:42
|
||||||
|
|
|
||||||
|
LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
note: required by a bound in `build_check_ensures`
|
||||||
|
--> $SRC_DIR/core/src/contracts.rs:LL:COL
|
||||||
|
help: consider annotating `Baz` with `#[derive(Copy)]`
|
||||||
|
|
|
||||||
|
LL + #[derive(Copy)]
|
||||||
|
LL | struct Baz {
|
||||||
|
|
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error; 1 warning emitted
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0277`.
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -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 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||||
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
49
tests/ui/contracts/contracts-ensures-early-fn-exit.rs
Normal file
49
tests/ui/contracts/contracts-ensures-early-fn-exit.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
//@ revisions: unchk_pass chk_pass chk_fail_try chk_fail_ret chk_fail_yeet
|
||||||
|
//
|
||||||
|
//@ [unchk_pass] run-pass
|
||||||
|
//@ [chk_pass] run-pass
|
||||||
|
//@ [chk_fail_try] run-fail
|
||||||
|
//@ [chk_fail_ret] run-fail
|
||||||
|
//@ [chk_fail_yeet] run-fail
|
||||||
|
//
|
||||||
|
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||||
|
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_fail_try] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_fail_ret] compile-flags: -Zcontract-checks=yes
|
||||||
|
//@ [chk_fail_yeet] compile-flags: -Zcontract-checks=yes
|
||||||
|
//! This test ensures that ensures clauses are checked for different return points of a function.
|
||||||
|
|
||||||
|
#![feature(contracts)]
|
||||||
|
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||||
|
#![feature(yeet_expr)]
|
||||||
|
|
||||||
|
/// This ensures will fail in different return points depending on the input.
|
||||||
|
#[core::contracts::ensures(|ret: &Option<u32>| ret.is_some())]
|
||||||
|
fn try_sum(x: u32, y: u32, z: u32) -> Option<u32> {
|
||||||
|
// 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);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue