Skip to content

Commit 3007437

Browse files
committed
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 # This is the commit message #2: Remove contracts keywords from diagnostic messages
1 parent 78bc83b commit 3007437

File tree

32 files changed

+529
-54
lines changed

32 files changed

+529
-54
lines changed

compiler/rustc_ast/src/ast.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3316,11 +3316,18 @@ pub struct Impl {
33163316
pub items: ThinVec<P<AssocItem>>,
33173317
}
33183318

3319+
#[derive(Clone, Encodable, Decodable, Debug, Default)]
3320+
pub struct FnContract {
3321+
pub requires: Option<P<Expr>>,
3322+
pub ensures: Option<P<Expr>>,
3323+
}
3324+
33193325
#[derive(Clone, Encodable, Decodable, Debug)]
33203326
pub struct Fn {
33213327
pub defaultness: Defaultness,
33223328
pub generics: Generics,
33233329
pub sig: FnSig,
3330+
pub contract: Option<P<FnContract>>,
33243331
pub body: Option<P<Block>>,
33253332
}
33263333

@@ -3618,7 +3625,7 @@ mod size_asserts {
36183625
static_assert_size!(Block, 32);
36193626
static_assert_size!(Expr, 72);
36203627
static_assert_size!(ExprKind, 40);
3621-
static_assert_size!(Fn, 160);
3628+
static_assert_size!(Fn, 168);
36223629
static_assert_size!(ForeignItem, 88);
36233630
static_assert_size!(ForeignItemKind, 16);
36243631
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
@@ -143,6 +143,10 @@ pub trait MutVisitor: Sized {
143143
walk_flat_map_assoc_item(self, i, ctxt)
144144
}
145145

146+
fn visit_contract(&mut self, c: &mut P<FnContract>) {
147+
walk_contract(self, c);
148+
}
149+
146150
fn visit_fn_decl(&mut self, d: &mut P<FnDecl>) {
147151
walk_fn_decl(self, d);
148152
}
@@ -973,6 +977,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
973977
}
974978
}
975979

980+
fn walk_contract<T: MutVisitor>(vis: &mut T, contract: &mut P<FnContract>) {
981+
let FnContract { requires, ensures } = contract.deref_mut();
982+
if let Some(pred) = requires {
983+
vis.visit_expr(pred);
984+
}
985+
if let Some(pred) = ensures {
986+
vis.visit_expr(pred);
987+
}
988+
}
989+
976990
fn walk_fn_decl<T: MutVisitor>(vis: &mut T, decl: &mut P<FnDecl>) {
977991
let FnDecl { inputs, output } = decl.deref_mut();
978992
inputs.flat_map_in_place(|param| vis.flat_map_param(param));
@@ -1205,8 +1219,11 @@ impl WalkItemKind for ItemKind {
12051219
ItemKind::Const(item) => {
12061220
visit_const_item(item, vis);
12071221
}
1208-
ItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
1222+
ItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => {
12091223
visit_defaultness(vis, defaultness);
1224+
if let Some(contract) = contract {
1225+
vis.visit_contract(contract)
1226+
};
12101227
vis.visit_fn(
12111228
FnKind::Fn(FnCtxt::Free, ident, sig, visibility, generics, body),
12121229
span,
@@ -1329,8 +1346,11 @@ impl WalkItemKind for AssocItemKind {
13291346
AssocItemKind::Const(item) => {
13301347
visit_const_item(item, visitor);
13311348
}
1332-
AssocItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
1349+
AssocItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => {
13331350
visit_defaultness(visitor, defaultness);
1351+
if let Some(contract) = contract {
1352+
visitor.visit_contract(contract);
1353+
}
13341354
visitor.visit_fn(
13351355
FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, visibility, generics, body),
13361356
span,
@@ -1476,8 +1496,11 @@ impl WalkItemKind for ForeignItemKind {
14761496
visitor.visit_ty(ty);
14771497
visit_opt(expr, |expr| visitor.visit_expr(expr));
14781498
}
1479-
ForeignItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
1499+
ForeignItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => {
14801500
visit_defaultness(visitor, defaultness);
1501+
if let Some(contract) = contract {
1502+
visitor.visit_contract(contract);
1503+
}
14811504
visitor.visit_fn(
14821505
FnKind::Fn(FnCtxt::Foreign, ident, sig, visibility, generics, body),
14831506
span,

compiler/rustc_ast/src/visit.rs

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

7078
/// E.g., `|x, y| body`.
7179
Closure(&'a ClosureBinder, &'a Option<CoroutineKind>, &'a FnDecl, &'a Expr),
@@ -74,7 +82,7 @@ pub enum FnKind<'a> {
7482
impl<'a> FnKind<'a> {
7583
pub fn header(&self) -> Option<&'a FnHeader> {
7684
match *self {
77-
FnKind::Fn(_, _, sig, _, _, _) => Some(&sig.header),
85+
FnKind::Fn(_, _, sig, _, _, _, _) => Some(&sig.header),
7886
FnKind::Closure(..) => None,
7987
}
8088
}
@@ -88,7 +96,7 @@ impl<'a> FnKind<'a> {
8896

8997
pub fn decl(&self) -> &'a FnDecl {
9098
match self {
91-
FnKind::Fn(_, _, sig, _, _, _) => &sig.decl,
99+
FnKind::Fn(_, _, sig, _, _, _, _) => &sig.decl,
92100
FnKind::Closure(_, _, decl, _) => decl,
93101
}
94102
}
@@ -188,6 +196,9 @@ pub trait Visitor<'ast>: Sized {
188196
fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result {
189197
walk_closure_binder(self, b)
190198
}
199+
fn visit_contract(&mut self, c: &'ast FnContract) -> Self::Result {
200+
walk_contract(self, c)
201+
}
191202
fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result {
192203
walk_where_predicate(self, p)
193204
}
@@ -374,8 +385,8 @@ impl WalkItemKind for ItemKind {
374385
try_visit!(visitor.visit_ty(ty));
375386
visit_opt!(visitor, visit_expr, expr);
376387
}
377-
ItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
378-
let kind = FnKind::Fn(FnCtxt::Free, ident, sig, vis, generics, body);
388+
ItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => {
389+
let kind = FnKind::Fn(FnCtxt::Free, ident, sig, vis, generics, contract, body);
379390
try_visit!(visitor.visit_fn(kind, span, id));
380391
}
381392
ItemKind::Mod(_unsafety, mod_kind) => match mod_kind {
@@ -715,8 +726,8 @@ impl WalkItemKind for ForeignItemKind {
715726
try_visit!(visitor.visit_ty(ty));
716727
visit_opt!(visitor, visit_expr, expr);
717728
}
718-
ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
719-
let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, body);
729+
ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => {
730+
let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, contract, body);
720731
try_visit!(visitor.visit_fn(kind, span, id));
721732
}
722733
ForeignItemKind::TyAlias(box TyAlias {
@@ -800,6 +811,17 @@ pub fn walk_closure_binder<'a, V: Visitor<'a>>(
800811
V::Result::output()
801812
}
802813

814+
pub fn walk_contract<'a, V: Visitor<'a>>(visitor: &mut V, c: &'a FnContract) -> V::Result {
815+
let FnContract { requires, ensures } = c;
816+
if let Some(pred) = requires {
817+
visitor.visit_expr(pred);
818+
}
819+
if let Some(pred) = ensures {
820+
visitor.visit_expr(pred);
821+
}
822+
V::Result::output()
823+
}
824+
803825
pub fn walk_where_predicate<'a, V: Visitor<'a>>(
804826
visitor: &mut V,
805827
predicate: &'a WherePredicate,
@@ -858,11 +880,20 @@ pub fn walk_fn_decl<'a, V: Visitor<'a>>(
858880

859881
pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Result {
860882
match kind {
861-
FnKind::Fn(_ctxt, _ident, FnSig { header, decl, span: _ }, _vis, generics, body) => {
883+
FnKind::Fn(
884+
_ctxt,
885+
_ident,
886+
FnSig { header, decl, span: _ },
887+
_vis,
888+
generics,
889+
contract,
890+
body,
891+
) => {
862892
// Identifier and visibility are visited as a part of the item.
863893
try_visit!(visitor.visit_fn_header(header));
864894
try_visit!(visitor.visit_generics(generics));
865895
try_visit!(visitor.visit_fn_decl(decl));
896+
visit_opt!(visitor, visit_contract, contract);
866897
visit_opt!(visitor, visit_block, body);
867898
}
868899
FnKind::Closure(binder, coroutine_kind, decl, body) => {
@@ -892,8 +923,9 @@ impl WalkItemKind for AssocItemKind {
892923
try_visit!(visitor.visit_ty(ty));
893924
visit_opt!(visitor, visit_expr, expr);
894925
}
895-
AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
896-
let kind = FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, body);
926+
AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => {
927+
let kind =
928+
FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, contract, body);
897929
try_visit!(visitor.visit_fn(kind, span, id));
898930
}
899931
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
@@ -314,7 +314,20 @@ impl<'hir> LoweringContext<'_, 'hir> {
314314
hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label))
315315
}
316316
ExprKind::Ret(e) => {
317-
let e = e.as_ref().map(|x| self.lower_expr(x));
317+
let mut e = e.as_ref().map(|x| self.lower_expr(x));
318+
if let Some(Some((span, fresh_ident))) = self
319+
.contract
320+
.as_ref()
321+
.map(|c| c.ensures.as_ref().map(|e| (e.expr.span, e.fresh_ident)))
322+
{
323+
let checker_fn = self.expr_ident(span, fresh_ident.0, fresh_ident.2);
324+
let args = if let Some(e) = e {
325+
std::slice::from_ref(e)
326+
} else {
327+
std::slice::from_ref(self.expr_unit(span))
328+
};
329+
e = Some(self.expr_call(span, checker_fn, args));
330+
}
318331
hir::ExprKind::Ret(e)
319332
}
320333
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
@@ -2125,7 +2138,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
21252138
self.arena.alloc(self.expr_call_mut(span, e, args))
21262139
}
21272140

2128-
fn expr_call_lang_item_fn_mut(
2141+
pub(super) fn expr_call_lang_item_fn_mut(
21292142
&mut self,
21302143
span: Span,
21312144
lang_item: hir::LangItem,
@@ -2135,7 +2148,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
21352148
self.expr_call_mut(span, path, args)
21362149
}
21372150

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

0 commit comments

Comments
 (0)