Skip to content

Commit 38eff16

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 Remove contracts keywords from diagnostic messages
1 parent 777def8 commit 38eff16

File tree

27 files changed

+405
-17
lines changed

27 files changed

+405
-17
lines changed

compiler/rustc_ast/src/ast.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -3348,11 +3348,18 @@ pub struct Impl {
33483348
pub items: ThinVec<P<AssocItem>>,
33493349
}
33503350

3351+
#[derive(Clone, Encodable, Decodable, Debug, Default)]
3352+
pub struct FnContract {
3353+
pub requires: Option<P<Expr>>,
3354+
pub ensures: Option<P<Expr>>,
3355+
}
3356+
33513357
#[derive(Clone, Encodable, Decodable, Debug)]
33523358
pub struct Fn {
33533359
pub defaultness: Defaultness,
33543360
pub generics: Generics,
33553361
pub sig: FnSig,
3362+
pub contract: Option<P<FnContract>>,
33563363
pub body: Option<P<Block>>,
33573364
}
33583365

@@ -3650,7 +3657,7 @@ mod size_asserts {
36503657
static_assert_size!(Block, 32);
36513658
static_assert_size!(Expr, 72);
36523659
static_assert_size!(ExprKind, 40);
3653-
static_assert_size!(Fn, 160);
3660+
static_assert_size!(Fn, 168);
36543661
static_assert_size!(ForeignItem, 88);
36553662
static_assert_size!(ForeignItemKind, 16);
36563663
static_assert_size!(GenericArg, 24);

compiler/rustc_ast/src/mut_visit.rs

+18-1
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
}
@@ -958,13 +962,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
958962
_ctxt,
959963
_ident,
960964
_vis,
961-
Fn { defaultness, generics, body, sig: FnSig { header, decl, span } },
965+
Fn { defaultness, generics, contract, body, sig: FnSig { header, decl, span } },
962966
) => {
963967
// Identifier and visibility are visited as a part of the item.
964968
visit_defaultness(vis, defaultness);
965969
vis.visit_fn_header(header);
966970
vis.visit_generics(generics);
967971
vis.visit_fn_decl(decl);
972+
if let Some(contract) = contract {
973+
vis.visit_contract(contract);
974+
}
968975
if let Some(body) = body {
969976
vis.visit_block(body);
970977
}
@@ -979,6 +986,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
979986
}
980987
}
981988

989+
fn walk_contract<T: MutVisitor>(vis: &mut T, contract: &mut P<FnContract>) {
990+
let FnContract { requires, ensures } = contract.deref_mut();
991+
if let Some(pred) = requires {
992+
vis.visit_expr(pred);
993+
}
994+
if let Some(pred) = ensures {
995+
vis.visit_expr(pred);
996+
}
997+
}
998+
982999
fn walk_fn_decl<T: MutVisitor>(vis: &mut T, decl: &mut P<FnDecl>) {
9831000
let FnDecl { inputs, output } = decl.deref_mut();
9841001
inputs.flat_map_in_place(|param| vis.flat_map_param(param));

compiler/rustc_ast/src/visit.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ pub trait Visitor<'ast>: Sized {
188188
fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result {
189189
walk_closure_binder(self, b)
190190
}
191+
fn visit_contract(&mut self, c: &'ast FnContract) -> Self::Result {
192+
walk_contract(self, c)
193+
}
191194
fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result {
192195
walk_where_predicate(self, p)
193196
}
@@ -800,6 +803,17 @@ pub fn walk_closure_binder<'a, V: Visitor<'a>>(
800803
V::Result::output()
801804
}
802805

806+
pub fn walk_contract<'a, V: Visitor<'a>>(visitor: &mut V, c: &'a FnContract) -> V::Result {
807+
let FnContract { requires, ensures } = c;
808+
if let Some(pred) = requires {
809+
visitor.visit_expr(pred);
810+
}
811+
if let Some(pred) = ensures {
812+
visitor.visit_expr(pred);
813+
}
814+
V::Result::output()
815+
}
816+
803817
pub fn walk_where_predicate<'a, V: Visitor<'a>>(
804818
visitor: &mut V,
805819
predicate: &'a WherePredicate,
@@ -862,12 +876,13 @@ pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Resu
862876
_ctxt,
863877
_ident,
864878
_vis,
865-
Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, generics, body },
879+
Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, generics, contract, body },
866880
) => {
867881
// Identifier and visibility are visited as a part of the item.
868882
try_visit!(visitor.visit_fn_header(header));
869883
try_visit!(visitor.visit_generics(generics));
870884
try_visit!(visitor.visit_fn_decl(decl));
885+
visit_opt!(visitor, visit_contract, contract);
871886
visit_opt!(visitor, visit_block, body);
872887
}
873888
FnKind::Closure(binder, coroutine_kind, decl, body) => {

compiler/rustc_ast_lowering/src/expr.rs

+14-1
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()),

compiler/rustc_ast_lowering/src/item.rs

+108-4
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
@@ -1054,10 +1087,81 @@ impl<'hir> LoweringContext<'_, 'hir> {
10541087
body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
10551088
) -> hir::BodyId {
10561089
self.lower_body(|this| {
1057-
(
1058-
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))),
1059-
body(this),
1060-
)
1090+
let params =
1091+
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x)));
1092+
let result = body(this);
1093+
1094+
let opt_contract = this.contract.take();
1095+
1096+
// { body }
1097+
// ==>
1098+
// { rustc_contract_requires(PRECOND); { body } }
1099+
let result: hir::Expr<'hir> = if let Some(contract) = opt_contract {
1100+
let lit_unit = |this: &mut LoweringContext<'_, 'hir>| {
1101+
this.expr(contract.span, hir::ExprKind::Tup(&[]))
1102+
};
1103+
1104+
let precond: hir::Stmt<'hir> = if let Some(req) = contract.requires {
1105+
let lowered_req = this.lower_expr_mut(&req);
1106+
let precond = this.expr_call_lang_item_fn_mut(
1107+
req.span,
1108+
hir::LangItem::ContractCheckRequires,
1109+
&*arena_vec![this; lowered_req],
1110+
);
1111+
this.stmt_expr(req.span, precond)
1112+
} else {
1113+
let u = lit_unit(this);
1114+
this.stmt_expr(contract.span, u)
1115+
};
1116+
1117+
let (postcond_checker, result) = if let Some(ens) = contract.ensures {
1118+
let crate::FnContractLoweringEnsures { expr: ens, fresh_ident } = ens;
1119+
let lowered_ens: hir::Expr<'hir> = this.lower_expr_mut(&ens);
1120+
let postcond_checker = this.expr_call_lang_item_fn(
1121+
ens.span,
1122+
hir::LangItem::ContractBuildCheckEnsures,
1123+
&*arena_vec![this; lowered_ens],
1124+
);
1125+
let checker_binding_pat = fresh_ident.1;
1126+
(
1127+
this.stmt_let_pat(
1128+
None,
1129+
ens.span,
1130+
Some(postcond_checker),
1131+
this.arena.alloc(checker_binding_pat),
1132+
hir::LocalSource::Contract,
1133+
),
1134+
{
1135+
let checker_fn =
1136+
this.expr_ident(ens.span, fresh_ident.0, fresh_ident.2);
1137+
let span = this.mark_span_with_reason(
1138+
DesugaringKind::Contract,
1139+
ens.span,
1140+
None,
1141+
);
1142+
this.expr_call_mut(
1143+
span,
1144+
checker_fn,
1145+
std::slice::from_ref(this.arena.alloc(result)),
1146+
)
1147+
},
1148+
)
1149+
} else {
1150+
let u = lit_unit(this);
1151+
(this.stmt_expr(contract.span, u), result)
1152+
};
1153+
1154+
let block = this.block_all(
1155+
contract.span,
1156+
arena_vec![this; precond, postcond_checker],
1157+
Some(this.arena.alloc(result)),
1158+
);
1159+
this.expr_block(block)
1160+
} else {
1161+
result
1162+
};
1163+
1164+
(params, result)
10611165
})
10621166
}
10631167

compiler/rustc_ast_lowering/src/lib.rs

+20
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ mod path;
8686

8787
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
8888

89+
#[derive(Debug, Clone)]
90+
struct FnContractLoweringInfo<'hir> {
91+
pub span: Span,
92+
pub requires: Option<ast::ptr::P<ast::Expr>>,
93+
pub ensures: Option<FnContractLoweringEnsures<'hir>>,
94+
}
95+
96+
#[derive(Debug, Clone)]
97+
struct FnContractLoweringEnsures<'hir> {
98+
expr: ast::ptr::P<ast::Expr>,
99+
fresh_ident: (Ident, hir::Pat<'hir>, HirId),
100+
}
101+
89102
struct LoweringContext<'a, 'hir> {
90103
tcx: TyCtxt<'hir>,
91104
resolver: &'a mut ResolverAstLowering,
@@ -100,6 +113,8 @@ struct LoweringContext<'a, 'hir> {
100113
/// Collect items that were created by lowering the current owner.
101114
children: Vec<(LocalDefId, hir::MaybeOwner<'hir>)>,
102115

116+
contract: Option<FnContractLoweringInfo<'hir>>,
117+
103118
coroutine_kind: Option<hir::CoroutineKind>,
104119

105120
/// When inside an `async` context, this is the `HirId` of the
@@ -148,6 +163,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
148163
bodies: Vec::new(),
149164
attrs: SortedMap::default(),
150165
children: Vec::default(),
166+
contract: None,
151167
current_hir_id_owner: hir::CRATE_OWNER_ID,
152168
item_local_id_counter: hir::ItemLocalId::ZERO,
153169
ident_and_label_to_local_id: Default::default(),
@@ -834,12 +850,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
834850
let was_in_loop_condition = self.is_in_loop_condition;
835851
self.is_in_loop_condition = false;
836852

853+
let old_contract = self.contract.take();
854+
837855
let catch_scope = self.catch_scope.take();
838856
let loop_scope = self.loop_scope.take();
839857
let ret = f(self);
840858
self.catch_scope = catch_scope;
841859
self.loop_scope = loop_scope;
842860

861+
self.contract = old_contract;
862+
843863
self.is_in_loop_condition = was_in_loop_condition;
844864

845865
self.current_item = current_item;

compiler/rustc_ast_passes/src/ast_validation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
917917
walk_list!(self, visit_attribute, &item.attrs);
918918
return; // Avoid visiting again.
919919
}
920-
ItemKind::Fn(func @ box Fn { defaultness, generics: _, sig, body }) => {
920+
ItemKind::Fn(func @ box Fn { defaultness, generics: _, sig, contract: _, body }) => {
921921
self.check_defaultness(item.span, *defaultness);
922922

923923
let is_intrinsic =

compiler/rustc_ast_pretty/src/pprust/state/item.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -650,13 +650,17 @@ impl<'a> State<'a> {
650650
attrs: &[ast::Attribute],
651651
func: &ast::Fn,
652652
) {
653-
let ast::Fn { defaultness, generics, sig, body } = func;
653+
let ast::Fn { defaultness, generics, sig, contract, body } = func;
654654
if body.is_some() {
655655
self.head("");
656656
}
657657
self.print_visibility(vis);
658658
self.print_defaultness(*defaultness);
659659
self.print_fn(&sig.decl, sig.header, Some(name), generics);
660+
if let Some(contract) = &contract {
661+
self.nbsp();
662+
self.print_contract(contract);
663+
}
660664
if let Some(body) = body {
661665
self.nbsp();
662666
self.print_block_with_attrs(body, attrs);
@@ -665,6 +669,21 @@ impl<'a> State<'a> {
665669
}
666670
}
667671

672+
fn print_contract(&mut self, contract: &ast::FnContract) {
673+
if let Some(pred) = &contract.requires {
674+
self.word("rustc_requires");
675+
self.popen();
676+
self.print_expr(pred, FixupContext::default());
677+
self.pclose();
678+
}
679+
if let Some(pred) = &contract.ensures {
680+
self.word("rustc_ensures");
681+
self.popen();
682+
self.print_expr(pred, FixupContext::default());
683+
self.pclose();
684+
}
685+
}
686+
668687
pub(crate) fn print_fn(
669688
&mut self,
670689
decl: &ast::FnDecl,

compiler/rustc_builtin_macros/src/alloc_error_handler.rs

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span
8585
defaultness: ast::Defaultness::Final,
8686
sig,
8787
generics: Generics::default(),
88+
contract: None,
8889
body,
8990
}));
9091

compiler/rustc_builtin_macros/src/deriving/generic/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,7 @@ impl<'a> MethodDef<'a> {
10341034
defaultness,
10351035
sig,
10361036
generics: fn_generics,
1037+
contract: None,
10371038
body: Some(body_block),
10381039
})),
10391040
tokens: None,

compiler/rustc_builtin_macros/src/global_allocator.rs

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ impl AllocFnFactory<'_, '_> {
8181
defaultness: ast::Defaultness::Final,
8282
sig,
8383
generics: Generics::default(),
84+
contract: None,
8485
body,
8586
}));
8687
let item = self.cx.item(

0 commit comments

Comments
 (0)