1use std::sync::Arc;
2
3use rustc_hir::attrs::lang_items::LangItem;
4use thin_vec::thin_vec;
5
6use crate::LoweringContext;
7
8impl<'hir> LoweringContext<'_, 'hir> {
9 pub(super) fn lower_contract(
20 &mut self,
21 body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>,
22 contract: &rustc_ast::FnContract,
23 ) -> rustc_hir::Expr<'hir> {
24 let contract_decls = self.lower_decls(contract);
28
29 match (&contract.requires, &contract.ensures) {
30 (Some(req), Some(ens)) => {
31 let precond = self.lower_precond(req);
55 let postcond_checker = self.lower_postcond_checker(ens);
56
57 let contract_check = self.lower_contract_check_with_postcond(
58 contract_decls,
59 Some(precond),
60 postcond_checker,
61 );
62
63 let wrapped_body =
64 self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span);
65 self.expr_block(wrapped_body)
66 }
67 (None, Some(ens)) => {
68 let postcond_checker = self.lower_postcond_checker(ens);
90 let contract_check =
91 self.lower_contract_check_with_postcond(contract_decls, None, postcond_checker);
92
93 let wrapped_body =
94 self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span);
95 self.expr_block(wrapped_body)
96 }
97 (Some(req), None) => {
98 let precond = self.lower_precond(req);
112 let precond_check = self.lower_contract_check_just_precond(contract_decls, precond);
113
114 let body = self.arena.alloc(body(self));
115
116 let wrapped_body = self.block_all(
118 body.span,
119 self.arena.alloc_from_iter([precond_check].into_iter()),
120 Some(body),
121 );
122 self.expr_block(wrapped_body)
123 }
124 (None, None) => body(self),
125 }
126 }
127
128 fn lower_decls(&mut self, contract: &rustc_ast::FnContract) -> &'hir [rustc_hir::Stmt<'hir>] {
129 let (decls, decls_tail) = self.lower_stmts(&contract.declarations);
130
131 if let Some(e) = decls_tail {
132 let tail = self.stmt_expr(e.span, *e);
134 self.arena.alloc_from_iter(decls.into_iter().map(|d| *d).chain([tail].into_iter()))
135 } else {
136 decls
137 }
138 }
139
140 fn lower_precond(&mut self, req: &Box<rustc_ast::Expr>) -> rustc_hir::Stmt<'hir> {
142 let lowered_req = self.lower_expr_mut(&req);
143 let req_span = self.mark_span_with_reason(
144 rustc_span::DesugaringKind::Contract,
145 lowered_req.span,
146 Some(Arc::clone(&self.allow_contracts)),
147 );
148 let precond = self.expr_call_lang_item_fn_mut(
149 req_span,
150 LangItem::ContractCheckRequires,
151 &*self.arena.alloc_from_iter([lowered_req])arena_vec![self; lowered_req],
152 );
153 self.stmt_expr(req.span, precond)
154 }
155
156 fn lower_postcond_checker(
157 &mut self,
158 ens: &Box<rustc_ast::Expr>,
159 ) -> &'hir rustc_hir::Expr<'hir> {
160 let ens_span = self.lower_span(ens.span);
161 let ens_span = self.mark_span_with_reason(
162 rustc_span::DesugaringKind::Contract,
163 ens_span,
164 Some(Arc::clone(&self.allow_contracts)),
165 );
166 let lowered_ens = self.lower_expr_mut(&ens);
167 self.expr_call_lang_item_fn(
168 ens_span,
169 LangItem::ContractBuildCheckEnsures,
170 &*self.arena.alloc_from_iter([lowered_ens])arena_vec![self; lowered_ens],
171 )
172 }
173
174 fn lower_contract_check_just_precond(
175 &mut self,
176 contract_decls: &'hir [rustc_hir::Stmt<'hir>],
177 precond: rustc_hir::Stmt<'hir>,
178 ) -> rustc_hir::Stmt<'hir> {
179 let stmts = self
180 .arena
181 .alloc_from_iter(contract_decls.into_iter().map(|d| *d).chain([precond].into_iter()));
182
183 let then_block_stmts = self.block_all(precond.span, stmts, None);
184 let then_block = self.arena.alloc(self.expr_block(&then_block_stmts));
185
186 let precond_check = rustc_hir::ExprKind::If(
187 self.arena.alloc(self.expr_bool_literal(precond.span, self.tcx.sess.contract_checks())),
188 then_block,
189 None,
190 );
191
192 let precond_check = self.expr(precond.span, precond_check);
193 self.stmt_expr(precond.span, precond_check)
194 }
195
196 fn lower_contract_check_with_postcond(
197 &mut self,
198 contract_decls: &'hir [rustc_hir::Stmt<'hir>],
199 precond: Option<rustc_hir::Stmt<'hir>>,
200 postcond_checker: &'hir rustc_hir::Expr<'hir>,
201 ) -> &'hir rustc_hir::Expr<'hir> {
202 let stmts = self
203 .arena
204 .alloc_from_iter(contract_decls.into_iter().map(|d| *d).chain(precond.into_iter()));
205 let span = match precond {
206 Some(precond) => precond.span,
207 None => postcond_checker.span,
208 };
209
210 let postcond_checker = self.arena.alloc(self.expr_enum_variant_lang_item(
211 postcond_checker.span,
212 rustc_hir::attrs::lang_items::LangItem::OptionSome,
213 &*self.arena.alloc_from_iter([*postcond_checker])arena_vec![self; *postcond_checker],
214 ));
215 let then_block_stmts = self.block_all(span, stmts, Some(postcond_checker));
216 let then_block = self.arena.alloc(self.expr_block(&then_block_stmts));
217
218 let none_expr = self.arena.alloc(self.expr_enum_variant_lang_item(
219 postcond_checker.span,
220 rustc_hir::attrs::lang_items::LangItem::OptionNone,
221 Default::default(),
222 ));
223 let else_block = self.block_expr(none_expr);
224 let else_block = self.arena.alloc(self.expr_block(else_block));
225
226 let contract_check = rustc_hir::ExprKind::If(
227 self.arena.alloc(self.expr_bool_literal(span, self.tcx.sess.contract_checks())),
228 then_block,
229 Some(else_block),
230 );
231 self.arena.alloc(self.expr(span, contract_check))
232 }
233
234 fn wrap_body_with_contract_check(
235 &mut self,
236 body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>,
237 contract_check: &'hir rustc_hir::Expr<'hir>,
238 postcond_span: rustc_span::Span,
239 ) -> &'hir rustc_hir::Block<'hir> {
240 let check_ident: rustc_span::Ident =
241 rustc_span::Ident::from_str_and_span("__ensures_checker", postcond_span);
242 let (check_hir_id, postcond_decl) = {
243 let (checker_pat, check_hir_id) = self.pat_ident_binding_mode_mut(
245 postcond_span,
246 check_ident,
247 rustc_hir::BindingMode::NONE,
248 );
249 (
250 check_hir_id,
251 self.stmt_let_pat(
252 None,
253 postcond_span,
254 Some(contract_check),
255 self.arena.alloc(checker_pat),
256 rustc_hir::LocalSource::Contract,
257 ),
258 )
259 };
260
261 self.contract_ensures = Some((postcond_span, check_ident, check_hir_id));
264 let body = self.arena.alloc(body(self));
265
266 let body = self.inject_ensures_check(body, postcond_span, check_ident, check_hir_id);
268
269 let wrapped_body = self.block_all(
271 body.span,
272 self.arena.alloc_from_iter([postcond_decl].into_iter()),
273 Some(body),
274 );
275 wrapped_body
276 }
277
278 pub(super) fn checked_return(
281 &mut self,
282 opt_expr: Option<&'hir rustc_hir::Expr<'hir>>,
283 ) -> rustc_hir::ExprKind<'hir> {
284 let checked_ret =
285 if let Some((check_span, check_ident, check_hir_id)) = self.contract_ensures {
286 let expr = opt_expr.unwrap_or_else(|| self.expr_unit(check_span));
287 Some(self.inject_ensures_check(expr, check_span, check_ident, check_hir_id))
288 } else {
289 opt_expr
290 };
291 rustc_hir::ExprKind::Ret(checked_ret)
292 }
293
294 pub(super) fn inject_ensures_check(
296 &mut self,
297 expr: &'hir rustc_hir::Expr<'hir>,
298 span: rustc_span::Span,
299 cond_ident: rustc_span::Ident,
300 cond_hir_id: rustc_hir::HirId,
301 ) -> &'hir rustc_hir::Expr<'hir> {
302 let ret_ident: rustc_span::Ident = rustc_span::Ident::from_str_and_span("__ret", span);
312
313 let (ret_pat, ret_hir_id) =
315 self.pat_ident_binding_mode_mut(span, ret_ident, rustc_hir::BindingMode::NONE);
316
317 let ret_stmt = self.stmt_let_pat(
318 None,
319 span,
320 Some(expr),
321 self.arena.alloc(ret_pat),
322 rustc_hir::LocalSource::Contract,
323 );
324
325 let ret = self.expr_ident(span, ret_ident, ret_hir_id);
326
327 let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id);
328 let contract_check = self.expr_call_lang_item_fn_mut(
329 span,
330 LangItem::ContractCheckEnsures,
331 self.arena.alloc_from_iter([*cond_fn, *ret])arena_vec![self; *cond_fn, *ret],
332 );
333 let contract_check = self.arena.alloc(contract_check);
334 let call_expr = self.block_expr_block(contract_check);
335
336 let ret = self.expr_ident(span, ret_ident, ret_hir_id);
339 let ret_block = self.block_expr_block(ret);
340
341 let contracts_enabled: rustc_hir::Expr<'_> =
342 self.expr_bool_literal(span, self.tcx.sess.contract_checks());
343 let contract_check = self.arena.alloc(self.expr(
344 span,
345 rustc_hir::ExprKind::If(
346 self.arena.alloc(contracts_enabled),
347 call_expr,
348 Some(ret_block),
349 ),
350 ));
351
352 let attrs: rustc_ast::AttrVec = {
let len = [()].len();
let mut vec = ::thin_vec::ThinVec::with_capacity(len);
vec.push(self.unreachable_code_attr(span));
vec
}thin_vec![self.unreachable_code_attr(span)];
353 self.lower_attrs(contract_check.hir_id, &attrs, span, rustc_hir::Target::Expression);
354
355 let ret_block = self.block_all(span, self.arena.alloc_from_iter([ret_stmt])arena_vec![self; ret_stmt], Some(contract_check));
356 self.arena.alloc(self.expr_block(self.arena.alloc(ret_block)))
357 }
358}