1
Fork 0

Express contracts as part of function header and lower it to the contract lang items

includes post-developed commit: do not suggest internal-only keywords as corrections to parse failures.

includes post-developed commit: removed tabs that creeped in into rustfmt tool source code.

includes post-developed commit, placating rustfmt self dogfooding.

includes post-developed commit: add backquotes to prevent markdown checking from trying to treat an attr as a markdown hyperlink/

includes post-developed commit: fix lowering to keep contracts from being erroneously inherited by nested bodies (like closures).

Rebase Conflicts:
 - compiler/rustc_parse/src/parser/diagnostics.rs
 - compiler/rustc_parse/src/parser/item.rs
 - compiler/rustc_span/src/hygiene.rs

Remove contracts keywords from diagnostic messages
This commit is contained in:
Celina G. Val 2025-01-08 16:38:25 -08:00
parent 777def87d5
commit 38eff16d0a
27 changed files with 405 additions and 17 deletions

View file

@ -3348,11 +3348,18 @@ pub struct Impl {
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)]
pub struct Fn {
pub defaultness: Defaultness,
pub generics: Generics,
pub sig: FnSig,
pub contract: Option<P<FnContract>>,
pub body: Option<P<Block>>,
}
@ -3650,7 +3657,7 @@ mod size_asserts {
static_assert_size!(Block, 32);
static_assert_size!(Expr, 72);
static_assert_size!(ExprKind, 40);
static_assert_size!(Fn, 160);
static_assert_size!(Fn, 168);
static_assert_size!(ForeignItem, 88);
static_assert_size!(ForeignItemKind, 16);
static_assert_size!(GenericArg, 24);

View file

@ -143,6 +143,10 @@ pub trait MutVisitor: Sized {
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>) {
walk_fn_decl(self, d);
}
@ -958,13 +962,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
_ctxt,
_ident,
_vis,
Fn { defaultness, generics, body, sig: FnSig { header, decl, span } },
Fn { defaultness, generics, contract, body, sig: FnSig { header, decl, span } },
) => {
// Identifier and visibility are visited as a part of the item.
visit_defaultness(vis, defaultness);
vis.visit_fn_header(header);
vis.visit_generics(generics);
vis.visit_fn_decl(decl);
if let Some(contract) = contract {
vis.visit_contract(contract);
}
if let Some(body) = body {
vis.visit_block(body);
}
@ -979,6 +986,16 @@ fn walk_fn<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>) {
let FnDecl { inputs, output } = decl.deref_mut();
inputs.flat_map_in_place(|param| vis.flat_map_param(param));

View file

@ -188,6 +188,9 @@ pub trait Visitor<'ast>: Sized {
fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result {
walk_closure_binder(self, b)
}
fn visit_contract(&mut self, c: &'ast FnContract) -> Self::Result {
walk_contract(self, c)
}
fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result {
walk_where_predicate(self, p)
}
@ -800,6 +803,17 @@ pub fn walk_closure_binder<'a, V: Visitor<'a>>(
V::Result::output()
}
pub fn walk_contract<'a, V: Visitor<'a>>(visitor: &mut V, c: &'a FnContract) -> V::Result {
let FnContract { requires, ensures } = c;
if let Some(pred) = requires {
visitor.visit_expr(pred);
}
if let Some(pred) = ensures {
visitor.visit_expr(pred);
}
V::Result::output()
}
pub fn walk_where_predicate<'a, V: Visitor<'a>>(
visitor: &mut V,
predicate: &'a WherePredicate,
@ -862,12 +876,13 @@ pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Resu
_ctxt,
_ident,
_vis,
Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, generics, body },
Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, generics, contract, body },
) => {
// Identifier and visibility are visited as a part of the item.
try_visit!(visitor.visit_fn_header(header));
try_visit!(visitor.visit_generics(generics));
try_visit!(visitor.visit_fn_decl(decl));
visit_opt!(visitor, visit_contract, contract);
visit_opt!(visitor, visit_block, body);
}
FnKind::Closure(binder, coroutine_kind, decl, body) => {

View file

@ -314,7 +314,20 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label))
}
ExprKind::Ret(e) => {
let e = e.as_ref().map(|x| self.lower_expr(x));
let mut e = e.as_ref().map(|x| self.lower_expr(x));
if let Some(Some((span, fresh_ident))) = self
.contract
.as_ref()
.map(|c| c.ensures.as_ref().map(|e| (e.expr.span, e.fresh_ident)))
{
let checker_fn = self.expr_ident(span, fresh_ident.0, fresh_ident.2);
let args = if let Some(e) = e {
std::slice::from_ref(e)
} else {
std::slice::from_ref(self.expr_unit(span))
};
e = Some(self.expr_call(span, checker_fn, args));
}
hir::ExprKind::Ret(e)
}
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),

View file

@ -207,9 +207,42 @@ impl<'hir> LoweringContext<'_, 'hir> {
sig: FnSig { decl, header, span: fn_sig_span },
generics,
body,
contract,
..
}) => {
self.with_new_scopes(*fn_sig_span, |this| {
assert!(this.contract.is_none());
if let Some(contract) = contract {
let requires = contract.requires.clone();
let ensures = contract.ensures.clone();
let ensures = if let Some(ens) = ensures {
// FIXME: this needs to be a fresh (or illegal) identifier to prevent
// accidental capture of a parameter or global variable.
let checker_ident: Ident =
Ident::from_str_and_span("__ensures_checker", ens.span);
let (checker_pat, checker_hir_id) = this.pat_ident_binding_mode_mut(
ens.span,
checker_ident,
hir::BindingMode::NONE,
);
Some(crate::FnContractLoweringEnsures {
expr: ens,
fresh_ident: (checker_ident, checker_pat, checker_hir_id),
})
} else {
None
};
// Note: `with_new_scopes` will reinstall the outer
// item's contract (if any) after its callback finishes.
this.contract.replace(crate::FnContractLoweringInfo {
span,
requires,
ensures,
});
}
// Note: we don't need to change the return type from `T` to
// `impl Future<Output = T>` here because lower_body
// only cares about the input argument patterns in the function
@ -1054,10 +1087,81 @@ impl<'hir> LoweringContext<'_, 'hir> {
body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
) -> hir::BodyId {
self.lower_body(|this| {
(
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))),
body(this),
)
let params =
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x)));
let result = body(this);
let opt_contract = this.contract.take();
// { body }
// ==>
// { rustc_contract_requires(PRECOND); { body } }
let result: hir::Expr<'hir> = if let Some(contract) = opt_contract {
let lit_unit = |this: &mut LoweringContext<'_, 'hir>| {
this.expr(contract.span, hir::ExprKind::Tup(&[]))
};
let precond: hir::Stmt<'hir> = if let Some(req) = contract.requires {
let lowered_req = this.lower_expr_mut(&req);
let precond = this.expr_call_lang_item_fn_mut(
req.span,
hir::LangItem::ContractCheckRequires,
&*arena_vec![this; lowered_req],
);
this.stmt_expr(req.span, precond)
} else {
let u = lit_unit(this);
this.stmt_expr(contract.span, u)
};
let (postcond_checker, result) = if let Some(ens) = contract.ensures {
let crate::FnContractLoweringEnsures { expr: ens, fresh_ident } = ens;
let lowered_ens: hir::Expr<'hir> = this.lower_expr_mut(&ens);
let postcond_checker = this.expr_call_lang_item_fn(
ens.span,
hir::LangItem::ContractBuildCheckEnsures,
&*arena_vec![this; lowered_ens],
);
let checker_binding_pat = fresh_ident.1;
(
this.stmt_let_pat(
None,
ens.span,
Some(postcond_checker),
this.arena.alloc(checker_binding_pat),
hir::LocalSource::Contract,
),
{
let checker_fn =
this.expr_ident(ens.span, fresh_ident.0, fresh_ident.2);
let span = this.mark_span_with_reason(
DesugaringKind::Contract,
ens.span,
None,
);
this.expr_call_mut(
span,
checker_fn,
std::slice::from_ref(this.arena.alloc(result)),
)
},
)
} else {
let u = lit_unit(this);
(this.stmt_expr(contract.span, u), result)
};
let block = this.block_all(
contract.span,
arena_vec![this; precond, postcond_checker],
Some(this.arena.alloc(result)),
);
this.expr_block(block)
} else {
result
};
(params, result)
})
}

View file

@ -86,6 +86,19 @@ mod path;
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
#[derive(Debug, Clone)]
struct FnContractLoweringInfo<'hir> {
pub span: Span,
pub requires: Option<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> {
tcx: TyCtxt<'hir>,
resolver: &'a mut ResolverAstLowering,
@ -100,6 +113,8 @@ struct LoweringContext<'a, 'hir> {
/// Collect items that were created by lowering the current owner.
children: Vec<(LocalDefId, hir::MaybeOwner<'hir>)>,
contract: Option<FnContractLoweringInfo<'hir>>,
coroutine_kind: Option<hir::CoroutineKind>,
/// When inside an `async` context, this is the `HirId` of the
@ -148,6 +163,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
bodies: Vec::new(),
attrs: SortedMap::default(),
children: Vec::default(),
contract: None,
current_hir_id_owner: hir::CRATE_OWNER_ID,
item_local_id_counter: hir::ItemLocalId::ZERO,
ident_and_label_to_local_id: Default::default(),
@ -834,12 +850,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
let was_in_loop_condition = self.is_in_loop_condition;
self.is_in_loop_condition = false;
let old_contract = self.contract.take();
let catch_scope = self.catch_scope.take();
let loop_scope = self.loop_scope.take();
let ret = f(self);
self.catch_scope = catch_scope;
self.loop_scope = loop_scope;
self.contract = old_contract;
self.is_in_loop_condition = was_in_loop_condition;
self.current_item = current_item;

View file

@ -917,7 +917,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
walk_list!(self, visit_attribute, &item.attrs);
return; // Avoid visiting again.
}
ItemKind::Fn(func @ box Fn { defaultness, generics: _, sig, body }) => {
ItemKind::Fn(func @ box Fn { defaultness, generics: _, sig, contract: _, body }) => {
self.check_defaultness(item.span, *defaultness);
let is_intrinsic =

View file

@ -650,13 +650,17 @@ impl<'a> State<'a> {
attrs: &[ast::Attribute],
func: &ast::Fn,
) {
let ast::Fn { defaultness, generics, sig, body } = func;
let ast::Fn { defaultness, generics, sig, contract, body } = func;
if body.is_some() {
self.head("");
}
self.print_visibility(vis);
self.print_defaultness(*defaultness);
self.print_fn(&sig.decl, sig.header, Some(name), generics);
if let Some(contract) = &contract {
self.nbsp();
self.print_contract(contract);
}
if let Some(body) = body {
self.nbsp();
self.print_block_with_attrs(body, attrs);
@ -665,6 +669,21 @@ impl<'a> State<'a> {
}
}
fn print_contract(&mut self, contract: &ast::FnContract) {
if let Some(pred) = &contract.requires {
self.word("rustc_requires");
self.popen();
self.print_expr(pred, FixupContext::default());
self.pclose();
}
if let Some(pred) = &contract.ensures {
self.word("rustc_ensures");
self.popen();
self.print_expr(pred, FixupContext::default());
self.pclose();
}
}
pub(crate) fn print_fn(
&mut self,
decl: &ast::FnDecl,

View file

@ -85,6 +85,7 @@ fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span
defaultness: ast::Defaultness::Final,
sig,
generics: Generics::default(),
contract: None,
body,
}));

View file

@ -1034,6 +1034,7 @@ impl<'a> MethodDef<'a> {
defaultness,
sig,
generics: fn_generics,
contract: None,
body: Some(body_block),
})),
tokens: None,

View file

@ -81,6 +81,7 @@ impl AllocFnFactory<'_, '_> {
defaultness: ast::Defaultness::Final,
sig,
generics: Generics::default(),
contract: None,
body,
}));
let item = self.cx.item(

View file

@ -344,6 +344,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
defaultness,
sig,
generics: ast::Generics::default(),
contract: None,
body: Some(main_body),
}));

View file

@ -2598,6 +2598,8 @@ pub enum LocalSource {
/// A desugared `expr = expr`, where the LHS is a tuple, struct, array or underscore expression.
/// The span is that of the `=` sign.
AssignDesugar(Span),
/// A contract `#[ensures(..)]` attribute injects a let binding for the check that runs at point of return.
Contract,
}
/// Hints at the original code for a `match _ { .. }`.

View file

@ -269,6 +269,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// diverging expression (e.g. it arose from desugaring of `try { return }`),
// we skip issuing a warning because it is autogenerated code.
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {}
// Likewise, do not lint unreachable code injected via contracts desugaring.
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::Contract) => {}
ExprKind::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
ExprKind::MethodCall(segment, ..) => {
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")

View file

@ -297,6 +297,28 @@ impl<'a> Parser<'a> {
})
}
/// Parses a rustc-internal fn contract
/// (`rustc_contract_requires(WWW) rustc_contract_ensures(ZZZ)`)
pub(super) fn parse_contract(
&mut self,
) -> PResult<'a, Option<rustc_ast::ptr::P<ast::FnContract>>> {
let requires = if self.eat_keyword_noexpect(exp!(RustcContractRequires).kw) {
Some(self.parse_expr()?)
} else {
None
};
let ensures = if self.eat_keyword_noexpect(exp!(RustcContractEnsures).kw) {
Some(self.parse_expr()?)
} else {
None
};
if requires.is_none() && ensures.is_none() {
Ok(None)
} else {
Ok(Some(rustc_ast::ptr::P(ast::FnContract { requires, ensures })))
}
}
/// Parses an optional where-clause.
///
/// ```ignore (only-for-syntax-highlight)

View file

@ -213,9 +213,12 @@ impl<'a> Parser<'a> {
self.parse_use_item()?
} else if self.check_fn_front_matter(check_pub, case) {
// FUNCTION ITEM
let (ident, sig, generics, body) =
let (ident, sig, generics, contract, body) =
self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?;
(ident, ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, body })))
(
ident,
ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, contract, body })),
)
} else if self.eat_keyword(exp!(Extern)) {
if self.eat_keyword(exp!(Crate)) {
// EXTERN CRATE
@ -2372,7 +2375,7 @@ impl<'a> Parser<'a> {
sig_lo: Span,
vis: &Visibility,
case: Case,
) -> PResult<'a, (Ident, FnSig, Generics, Option<P<Block>>)> {
) -> PResult<'a, (Ident, FnSig, Generics, Option<P<FnContract>>, Option<P<Block>>)> {
let fn_span = self.token.span;
let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn`
let ident = self.parse_ident()?; // `foo`
@ -2398,6 +2401,8 @@ impl<'a> Parser<'a> {
// inside `parse_fn_body()`.
let fn_params_end = self.prev_token.span.shrink_to_hi();
let contract = self.parse_contract()?;
generics.where_clause = self.parse_where_clause()?; // `where T: Ord`
// `fn_params_end` is needed only when it's followed by a where clause.
@ -2409,7 +2414,7 @@ impl<'a> Parser<'a> {
let body =
self.parse_fn_body(attrs, &ident, &mut sig_hi, fn_parse_mode.req_body, fn_params_end)?;
let fn_sig_span = sig_lo.to(sig_hi);
Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, body))
Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, contract, body))
}
/// Provide diagnostics when function body is not found

View file

@ -108,6 +108,8 @@ pub enum TokenType {
KwRef,
KwReturn,
KwReuse,
KwRustcContractEnsures,
KwRustcContractRequires,
KwSafe,
KwSelfUpper,
KwStatic,
@ -242,6 +244,8 @@ impl TokenType {
KwRef,
KwReturn,
KwReuse,
KwRustcContractEnsures,
KwRustcContractRequires,
KwSafe,
KwSelfUpper,
KwStatic,
@ -314,6 +318,8 @@ impl TokenType {
TokenType::KwRef => Some(kw::Ref),
TokenType::KwReturn => Some(kw::Return),
TokenType::KwReuse => Some(kw::Reuse),
TokenType::KwRustcContractEnsures => Some(kw::RustcContractEnsures),
TokenType::KwRustcContractRequires => Some(kw::RustcContractRequires),
TokenType::KwSafe => Some(kw::Safe),
TokenType::KwSelfUpper => Some(kw::SelfUpper),
TokenType::KwStatic => Some(kw::Static),
@ -544,6 +550,8 @@ macro_rules! exp {
(Ref) => { exp!(@kw, Ref, KwRef) };
(Return) => { exp!(@kw, Return, KwReturn) };
(Reuse) => { exp!(@kw, Reuse, KwReuse) };
(RustcContractEnsures) => { exp!(@kw, RustcContractEnsures, KwRustcContractEnsures) };
(RustcContractRequires) => { exp!(@kw, RustcContractRequires, KwRustcContractRequires) };
(Safe) => { exp!(@kw, Safe, KwSafe) };
(SelfUpper) => { exp!(@kw, SelfUpper, KwSelfUpper) };
(Static) => { exp!(@kw, Static, KwStatic) };

View file

@ -174,10 +174,13 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> {
_ctxt,
_ident,
_vis,
Fn { sig: FnSig { header, decl, span: _ }, generics, body, .. },
Fn { sig: FnSig { header, decl, span: _ }, generics, contract, body, .. },
) if let Some(coroutine_kind) = header.coroutine_kind => {
self.visit_fn_header(header);
self.visit_generics(generics);
if let Some(contract) = contract {
self.visit_contract(contract);
}
// For async functions, we need to create their inner defs inside of a
// closure to match their desugared representation. Besides that,

View file

@ -1019,7 +1019,7 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
// Create a label rib for the function.
this.with_label_rib(RibKind::FnOrCoroutine, |this| {
match fn_kind {
FnKind::Fn(_, _, _, Fn { sig, generics, body, .. }) => {
FnKind::Fn(_, _, _, Fn { sig, generics, contract, body, .. }) => {
this.visit_generics(generics);
let declaration = &sig.decl;
@ -1046,6 +1046,10 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
},
);
if let Some(contract) = contract {
this.visit_contract(contract);
}
if let Some(body) = body {
// Ignore errors in function bodies if this is rustdoc
// Be sure not to set this until the function signature has been resolved.

View file

@ -1163,6 +1163,8 @@ pub enum DesugaringKind {
WhileLoop,
/// `async Fn()` bound modifier
BoundModifier,
/// Calls to contract checks (`#[requires]` to precond, `#[ensures]` to postcond)
Contract,
}
impl DesugaringKind {
@ -1179,6 +1181,7 @@ impl DesugaringKind {
DesugaringKind::ForLoop => "`for` loop",
DesugaringKind::WhileLoop => "`while` loop",
DesugaringKind::BoundModifier => "trait bound modifier",
DesugaringKind::Contract => "contract check",
}
}
}

View file

@ -118,6 +118,8 @@ symbols! {
MacroRules: "macro_rules",
Raw: "raw",
Reuse: "reuse",
RustcContractEnsures: "rustc_contract_ensures",
RustcContractRequires: "rustc_contract_requires",
Safe: "safe",
Union: "union",
Yeet: "yeet",

View file

@ -113,8 +113,8 @@
#![feature(bigint_helper_methods)]
#![feature(bstr)]
#![feature(bstr_internals)]
#![feature(const_carrying_mul_add)]
#![feature(closure_track_caller)]
#![feature(const_carrying_mul_add)]
#![feature(const_eval_select)]
#![feature(core_intrinsics)]
#![feature(coverage_attribute)]

View file

@ -362,18 +362,21 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
defaultness: ld,
sig: lf,
generics: lg,
contract: lc,
body: lb,
}),
Fn(box ast::Fn {
defaultness: rd,
sig: rf,
generics: rg,
contract: rc,
body: rb,
}),
) => {
eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf)
&& eq_generics(lg, rg)
&& eq_opt_fn_contract(lc, rc)
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
},
(Mod(lu, lmk), Mod(ru, rmk)) => {
@ -497,18 +500,21 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
defaultness: ld,
sig: lf,
generics: lg,
contract: lc,
body: lb,
}),
Fn(box ast::Fn {
defaultness: rd,
sig: rf,
generics: rg,
contract: rc,
body: rb,
}),
) => {
eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf)
&& eq_generics(lg, rg)
&& eq_opt_fn_contract(lc, rc)
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
},
(
@ -559,18 +565,21 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
defaultness: ld,
sig: lf,
generics: lg,
contract: lc,
body: lb,
}),
Fn(box ast::Fn {
defaultness: rd,
sig: rf,
generics: rg,
contract: rc,
body: rb,
}),
) => {
eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf)
&& eq_generics(lg, rg)
&& eq_opt_fn_contract(lc, rc)
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
},
(
@ -653,6 +662,17 @@ pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
&& eq_ext(&l.ext, &r.ext)
}
pub fn eq_opt_fn_contract(l: &Option<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 {
over(&l.params, &r.params, eq_generic_param)
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {

View file

@ -0,0 +1,44 @@
//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post
//
//@ [unchk_pass] run-pass
//@ [unchk_fail_pre] run-pass
//@ [unchk_fail_post] run-pass
//@ [chk_pass] run-pass
//
//@ [chk_fail_pre] run-fail
//@ [chk_fail_post] run-fail
//
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no
//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no
//
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes
//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes
#![feature(rustc_contracts)]
fn nest(x: Baz) -> i32
rustc_contract_requires(|| x.baz > 0)
rustc_contract_ensures(|ret| *ret > 100)
{
loop {
return x.baz + 50;
}
}
struct Baz { baz: i32 }
const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 };
#[cfg(any(unchk_fail_post, chk_fail_post))]
const BAZ_FAIL_POST: Baz = Baz { baz: 10 };
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
const BAZ_FAIL_PRE: Baz = Baz { baz: -10 };
fn main() {
assert_eq!(nest(BAZ_PASS_PRE_POST), 150);
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
nest(BAZ_FAIL_PRE);
#[cfg(any(unchk_fail_post, chk_fail_post))]
nest(BAZ_FAIL_POST);
}

View file

@ -0,0 +1,42 @@
//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post
//
//@ [unchk_pass] run-pass
//@ [unchk_fail_pre] run-pass
//@ [unchk_fail_post] run-pass
//@ [chk_pass] run-pass
//
//@ [chk_fail_pre] run-fail
//@ [chk_fail_post] run-fail
//
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no
//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no
//
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes
//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes
#![feature(rustc_contracts)]
fn tail(x: Baz) -> i32
rustc_contract_requires(|| x.baz > 0)
rustc_contract_ensures(|ret| *ret > 100)
{
x.baz + 50
}
struct Baz { baz: i32 }
const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 };
#[cfg(any(unchk_fail_post, chk_fail_post))]
const BAZ_FAIL_POST: Baz = Baz { baz: 10 };
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
const BAZ_FAIL_PRE: Baz = Baz { baz: -10 };
fn main() {
assert_eq!(tail(BAZ_PASS_PRE_POST), 150);
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
tail(BAZ_FAIL_PRE);
#[cfg(any(unchk_fail_post, chk_fail_post))]
tail(BAZ_FAIL_POST);
}

View file

@ -0,0 +1,15 @@
//@ run-pass
//@ compile-flags: -Zcontract-checks=yes
#![feature(rustc_contracts)]
fn outer() -> i32
rustc_contract_ensures(|ret| *ret > 0)
{
let inner_closure = || -> i32 { 0 };
inner_closure();
10
}
fn main() {
outer();
}

View file

@ -0,0 +1,17 @@
//@ run-pass
//@ compile-flags: -Zcontract-checks=yes
#![feature(rustc_contracts)]
struct Outer { outer: std::cell::Cell<i32> }
fn outer(x: Outer)
rustc_contract_requires(|| x.outer.get() > 0)
{
let inner_closure = || { };
x.outer.set(0);
inner_closure();
}
fn main() {
outer(Outer { outer: 1.into() });
}