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

@ -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;