-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#[contracts::requires(...)] + #[contracts::ensures(...)] #128045
base: master
Are you sure you want to change the base?
Changes from all commits
b7195e3
78bc83b
3007437
46c3e38
89dbca3
6d82aaa
7baaa4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -1051,10 +1084,82 @@ 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 contract = this.contract.take(); | ||
|
||
// { body } | ||
// ==> | ||
// { rustc_contract_requires(PRECOND); { body } } | ||
let result: hir::Expr<'hir> = if let Some(_contract) = 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, _opt_ident, result) = if let Some(ens) = _contract.ensures { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably necessary for later in the PR, but, why is there a tuple field here that we just ignore There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I don't think I ever actually used it in the later commits. It was more that I had the info available from when I created the identifier and I wasn't sure if I might need it, so I threaded it through to at least this point...) |
||
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, | ||
), | ||
Some((fresh_ident.0, fresh_ident.2)), | ||
{ | ||
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), None, 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) | ||
}) | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return
s kind of duplicate logic with the trailing expression, something can be improved hereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes I agree. It should also be unified with how the Try operator is implemented. I.e. all three cases (
return
,?
, and the tail expression) should all call some common method so that code like this can be controlled in one place.