Skip to content

Commit 190f93d

Browse files
committed
intermediate step: express contracts as part of function header and lower it into the previously added 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).
1 parent 448cd54 commit 190f93d

File tree

31 files changed

+528
-53
lines changed

31 files changed

+528
-53
lines changed

compiler/rustc_ast/src/ast.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3353,11 +3353,18 @@ pub struct Impl {
33533353
pub items: ThinVec<P<AssocItem>>,
33543354
}
33553355

3356+
#[derive(Clone, Encodable, Decodable, Debug, Default)]
3357+
pub struct FnContract {
3358+
pub requires: Option<P<Expr>>,
3359+
pub ensures: Option<P<Expr>>,
3360+
}
3361+
33563362
#[derive(Clone, Encodable, Decodable, Debug)]
33573363
pub struct Fn {
33583364
pub defaultness: Defaultness,
33593365
pub generics: Generics,
33603366
pub sig: FnSig,
3367+
pub contract: Option<P<FnContract>>,
33613368
pub body: Option<P<Block>>,
33623369
}
33633370

@@ -3655,7 +3662,7 @@ mod size_asserts {
36553662
static_assert_size!(Block, 32);
36563663
static_assert_size!(Expr, 72);
36573664
static_assert_size!(ExprKind, 40);
3658-
static_assert_size!(Fn, 160);
3665+
static_assert_size!(Fn, 168);
36593666
static_assert_size!(ForeignItem, 88);
36603667
static_assert_size!(ForeignItemKind, 16);
36613668
static_assert_size!(GenericArg, 24);

compiler/rustc_ast/src/mut_visit.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ pub trait MutVisitor: Sized {
144144
walk_flat_map_assoc_item(self, i, ctxt)
145145
}
146146

147+
fn visit_contract(&mut self, c: &mut P<FnContract>) {
148+
walk_contract(self, c);
149+
}
150+
147151
fn visit_fn_decl(&mut self, d: &mut P<FnDecl>) {
148152
walk_fn_decl(self, d);
149153
}
@@ -969,6 +973,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
969973
}
970974
}
971975

976+
fn walk_contract<T: MutVisitor>(vis: &mut T, contract: &mut P<FnContract>) {
977+
let FnContract { requires, ensures } = contract.deref_mut();
978+
if let Some(pred) = requires {
979+
vis.visit_expr(pred);
980+
}
981+
if let Some(pred) = ensures {
982+
vis.visit_expr(pred);
983+
}
984+
}
985+
972986
fn walk_fn_decl<T: MutVisitor>(vis: &mut T, decl: &mut P<FnDecl>) {
973987
let FnDecl { inputs, output } = decl.deref_mut();
974988
inputs.flat_map_in_place(|param| vis.flat_map_param(param));
@@ -1201,8 +1215,11 @@ impl WalkItemKind for ItemKind {
12011215
ItemKind::Const(item) => {
12021216
visit_const_item(item, vis);
12031217
}
1204-
ItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
1218+
ItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => {
12051219
visit_defaultness(vis, defaultness);
1220+
if let Some(contract) = contract {
1221+
vis.visit_contract(contract)
1222+
};
12061223
vis.visit_fn(
12071224
FnKind::Fn(FnCtxt::Free, ident, sig, visibility, generics, body),
12081225
span,
@@ -1320,8 +1337,11 @@ impl WalkItemKind for AssocItemKind {
13201337
AssocItemKind::Const(item) => {
13211338
visit_const_item(item, visitor);
13221339
}
1323-
AssocItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
1340+
AssocItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => {
13241341
visit_defaultness(visitor, defaultness);
1342+
if let Some(contract) = contract {
1343+
visitor.visit_contract(contract);
1344+
}
13251345
visitor.visit_fn(
13261346
FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, visibility, generics, body),
13271347
span,
@@ -1467,8 +1487,11 @@ impl WalkItemKind for ForeignItemKind {
14671487
visitor.visit_ty(ty);
14681488
visit_opt(expr, |expr| visitor.visit_expr(expr));
14691489
}
1470-
ForeignItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
1490+
ForeignItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => {
14711491
visit_defaultness(visitor, defaultness);
1492+
if let Some(contract) = contract {
1493+
visitor.visit_contract(contract);
1494+
}
14721495
visitor.visit_fn(
14731496
FnKind::Fn(FnCtxt::Foreign, ident, sig, visibility, generics, body),
14741497
span,

compiler/rustc_ast/src/visit.rs

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,15 @@ impl BoundKind {
6666
#[derive(Copy, Clone, Debug)]
6767
pub enum FnKind<'a> {
6868
/// E.g., `fn foo()`, `fn foo(&self)`, or `extern "Abi" fn foo()`.
69-
Fn(FnCtxt, &'a Ident, &'a FnSig, &'a Visibility, &'a Generics, &'a Option<P<Block>>),
69+
Fn(
70+
FnCtxt,
71+
&'a Ident,
72+
&'a FnSig,
73+
&'a Visibility,
74+
&'a Generics,
75+
&'a Option<P<FnContract>>,
76+
&'a Option<P<Block>>,
77+
),
7078

7179
/// E.g., `|x, y| body`.
7280
Closure(&'a ClosureBinder, &'a Option<CoroutineKind>, &'a FnDecl, &'a Expr),
@@ -75,7 +83,7 @@ pub enum FnKind<'a> {
7583
impl<'a> FnKind<'a> {
7684
pub fn header(&self) -> Option<&'a FnHeader> {
7785
match *self {
78-
FnKind::Fn(_, _, sig, _, _, _) => Some(&sig.header),
86+
FnKind::Fn(_, _, sig, _, _, _, _) => Some(&sig.header),
7987
FnKind::Closure(..) => None,
8088
}
8189
}
@@ -89,7 +97,7 @@ impl<'a> FnKind<'a> {
8997

9098
pub fn decl(&self) -> &'a FnDecl {
9199
match self {
92-
FnKind::Fn(_, _, sig, _, _, _) => &sig.decl,
100+
FnKind::Fn(_, _, sig, _, _, _, _) => &sig.decl,
93101
FnKind::Closure(_, _, decl, _) => decl,
94102
}
95103
}
@@ -189,6 +197,9 @@ pub trait Visitor<'ast>: Sized {
189197
fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result {
190198
walk_closure_binder(self, b)
191199
}
200+
fn visit_contract(&mut self, c: &'ast FnContract) -> Self::Result {
201+
walk_contract(self, c)
202+
}
192203
fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result {
193204
walk_where_predicate(self, p)
194205
}
@@ -375,8 +386,8 @@ impl WalkItemKind for ItemKind {
375386
try_visit!(visitor.visit_ty(ty));
376387
visit_opt!(visitor, visit_expr, expr);
377388
}
378-
ItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
379-
let kind = FnKind::Fn(FnCtxt::Free, ident, sig, vis, generics, body);
389+
ItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => {
390+
let kind = FnKind::Fn(FnCtxt::Free, ident, sig, vis, generics, contract, body);
380391
try_visit!(visitor.visit_fn(kind, span, id));
381392
}
382393
ItemKind::Mod(_unsafety, mod_kind) => match mod_kind {
@@ -712,8 +723,8 @@ impl WalkItemKind for ForeignItemKind {
712723
try_visit!(visitor.visit_ty(ty));
713724
visit_opt!(visitor, visit_expr, expr);
714725
}
715-
ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
716-
let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, body);
726+
ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => {
727+
let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, contract, body);
717728
try_visit!(visitor.visit_fn(kind, span, id));
718729
}
719730
ForeignItemKind::TyAlias(box TyAlias {
@@ -797,6 +808,17 @@ pub fn walk_closure_binder<'a, V: Visitor<'a>>(
797808
V::Result::output()
798809
}
799810

811+
pub fn walk_contract<'a, V: Visitor<'a>>(visitor: &mut V, c: &'a FnContract) -> V::Result {
812+
let FnContract { requires, ensures } = c;
813+
if let Some(pred) = requires {
814+
visitor.visit_expr(pred);
815+
}
816+
if let Some(pred) = ensures {
817+
visitor.visit_expr(pred);
818+
}
819+
V::Result::output()
820+
}
821+
800822
pub fn walk_where_predicate<'a, V: Visitor<'a>>(
801823
visitor: &mut V,
802824
predicate: &'a WherePredicate,
@@ -855,11 +877,20 @@ pub fn walk_fn_decl<'a, V: Visitor<'a>>(
855877

856878
pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Result {
857879
match kind {
858-
FnKind::Fn(_ctxt, _ident, FnSig { header, decl, span: _ }, _vis, generics, body) => {
880+
FnKind::Fn(
881+
_ctxt,
882+
_ident,
883+
FnSig { header, decl, span: _ },
884+
_vis,
885+
generics,
886+
contract,
887+
body,
888+
) => {
859889
// Identifier and visibility are visited as a part of the item.
860890
try_visit!(visitor.visit_fn_header(header));
861891
try_visit!(visitor.visit_generics(generics));
862892
try_visit!(visitor.visit_fn_decl(decl));
893+
visit_opt!(visitor, visit_contract, contract);
863894
visit_opt!(visitor, visit_block, body);
864895
}
865896
FnKind::Closure(binder, coroutine_kind, decl, body) => {
@@ -889,8 +920,9 @@ impl WalkItemKind for AssocItemKind {
889920
try_visit!(visitor.visit_ty(ty));
890921
visit_opt!(visitor, visit_expr, expr);
891922
}
892-
AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
893-
let kind = FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, body);
923+
AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => {
924+
let kind =
925+
FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, contract, body);
894926
try_visit!(visitor.visit_fn(kind, span, id));
895927
}
896928
AssocItemKind::Type(box TyAlias {

compiler/rustc_ast_lowering/src/expr.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,20 @@ impl<'hir> LoweringContext<'_, 'hir> {
336336
hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label))
337337
}
338338
ExprKind::Ret(e) => {
339-
let e = e.as_ref().map(|x| self.lower_expr(x));
339+
let mut e = e.as_ref().map(|x| self.lower_expr(x));
340+
if let Some(Some((span, fresh_ident))) = self
341+
.contract
342+
.as_ref()
343+
.map(|c| c.ensures.as_ref().map(|e| (e.expr.span, e.fresh_ident)))
344+
{
345+
let checker_fn = self.expr_ident(span, fresh_ident.0, fresh_ident.2);
346+
let args = if let Some(e) = e {
347+
std::slice::from_ref(e)
348+
} else {
349+
std::slice::from_ref(self.expr_unit(span))
350+
};
351+
e = Some(self.expr_call(span, checker_fn, args));
352+
}
340353
hir::ExprKind::Ret(e)
341354
}
342355
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
@@ -2113,7 +2126,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
21132126
self.arena.alloc(self.expr_call_mut(span, e, args))
21142127
}
21152128

2116-
fn expr_call_lang_item_fn_mut(
2129+
pub(super) fn expr_call_lang_item_fn_mut(
21172130
&mut self,
21182131
span: Span,
21192132
lang_item: hir::LangItem,
@@ -2123,7 +2136,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
21232136
self.expr_call_mut(span, path, args)
21242137
}
21252138

2126-
fn expr_call_lang_item_fn(
2139+
pub(super) fn expr_call_lang_item_fn(
21272140
&mut self,
21282141
span: Span,
21292142
lang_item: hir::LangItem,

compiler/rustc_ast_lowering/src/item.rs

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,42 @@ impl<'hir> LoweringContext<'_, 'hir> {
208208
sig: FnSig { decl, header, span: fn_sig_span },
209209
generics,
210210
body,
211+
contract,
211212
..
212213
}) => {
213214
self.with_new_scopes(*fn_sig_span, |this| {
215+
assert!(this.contract.is_none());
216+
if let Some(contract) = contract {
217+
let requires = contract.requires.clone();
218+
let ensures = contract.ensures.clone();
219+
let ensures = if let Some(ens) = ensures {
220+
// FIXME: this needs to be a fresh (or illegal) identifier to prevent
221+
// accidental capture of a parameter or global variable.
222+
let checker_ident: Ident =
223+
Ident::from_str_and_span("__ensures_checker", ens.span);
224+
let (checker_pat, checker_hir_id) = this.pat_ident_binding_mode_mut(
225+
ens.span,
226+
checker_ident,
227+
hir::BindingMode::NONE,
228+
);
229+
230+
Some(crate::FnContractLoweringEnsures {
231+
expr: ens,
232+
fresh_ident: (checker_ident, checker_pat, checker_hir_id),
233+
})
234+
} else {
235+
None
236+
};
237+
238+
// Note: `with_new_scopes` will reinstall the outer
239+
// item's contract (if any) after its callback finishes.
240+
this.contract.replace(crate::FnContractLoweringInfo {
241+
span,
242+
requires,
243+
ensures,
244+
});
245+
}
246+
214247
// Note: we don't need to change the return type from `T` to
215248
// `impl Future<Output = T>` here because lower_body
216249
// only cares about the input argument patterns in the function
@@ -1048,10 +1081,82 @@ impl<'hir> LoweringContext<'_, 'hir> {
10481081
body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
10491082
) -> hir::BodyId {
10501083
self.lower_body(|this| {
1051-
(
1052-
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))),
1053-
body(this),
1054-
)
1084+
let params =
1085+
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x)));
1086+
let result = body(this);
1087+
1088+
let contract = this.contract.take();
1089+
1090+
// { body }
1091+
// ==>
1092+
// { rustc_contract_requires(PRECOND); { body } }
1093+
let result: hir::Expr<'hir> = if let Some(_contract) = contract {
1094+
let lit_unit = |this: &mut LoweringContext<'_, 'hir>| {
1095+
this.expr(_contract.span, hir::ExprKind::Tup(&[]))
1096+
};
1097+
1098+
let precond: hir::Stmt<'hir> = if let Some(req) = _contract.requires {
1099+
let lowered_req = this.lower_expr_mut(&req);
1100+
let precond = this.expr_call_lang_item_fn_mut(
1101+
req.span,
1102+
hir::LangItem::ContractCheckRequires,
1103+
&*arena_vec![this; lowered_req],
1104+
);
1105+
this.stmt_expr(req.span, precond)
1106+
} else {
1107+
let u = lit_unit(this);
1108+
this.stmt_expr(_contract.span, u)
1109+
};
1110+
1111+
let (postcond_checker, _opt_ident, result) = if let Some(ens) = _contract.ensures {
1112+
let crate::FnContractLoweringEnsures { expr: ens, fresh_ident } = ens;
1113+
let lowered_ens: hir::Expr<'hir> = this.lower_expr_mut(&ens);
1114+
let postcond_checker = this.expr_call_lang_item_fn(
1115+
ens.span,
1116+
hir::LangItem::ContractBuildCheckEnsures,
1117+
&*arena_vec![this; lowered_ens],
1118+
);
1119+
let checker_binding_pat = fresh_ident.1;
1120+
(
1121+
this.stmt_let_pat(
1122+
None,
1123+
ens.span,
1124+
Some(postcond_checker),
1125+
this.arena.alloc(checker_binding_pat),
1126+
hir::LocalSource::Contract,
1127+
),
1128+
Some((fresh_ident.0, fresh_ident.2)),
1129+
{
1130+
let checker_fn =
1131+
this.expr_ident(ens.span, fresh_ident.0, fresh_ident.2);
1132+
let span = this.mark_span_with_reason(
1133+
DesugaringKind::Contract,
1134+
ens.span,
1135+
None,
1136+
);
1137+
this.expr_call_mut(
1138+
span,
1139+
checker_fn,
1140+
std::slice::from_ref(this.arena.alloc(result)),
1141+
)
1142+
},
1143+
)
1144+
} else {
1145+
let u = lit_unit(this);
1146+
(this.stmt_expr(_contract.span, u), None, result)
1147+
};
1148+
1149+
let block = this.block_all(
1150+
_contract.span,
1151+
arena_vec![this; precond, postcond_checker],
1152+
Some(this.arena.alloc(result)),
1153+
);
1154+
this.expr_block(block)
1155+
} else {
1156+
result
1157+
};
1158+
1159+
(params, result)
10551160
})
10561161
}
10571162

0 commit comments

Comments
 (0)