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:
parent
777def87d5
commit
38eff16d0a
27 changed files with 405 additions and 17 deletions
|
@ -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()),
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue