Skip to content

Commit dee2b82

Browse files
committed
fix: place function bodies in labelled block expression instead of closures
fixes #5 #7 #8 taken from https://gitlab.com/karroffel/contracts/-/merge_requests/9/diffs
1 parent bee7c40 commit dee2b82

File tree

4 files changed

+117
-187
lines changed

4 files changed

+117
-187
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## Unreleased
88

9+
- Improve support for async functions and mutable borrows in contracts.
10+
911
## 0.6.4
1012

1113
- Add effective contract type to invariant violation messages.

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1-
# _Design By Contract_ for Rust
1+
# `contracts`
22

3-
[![License][license]][LICENSE] ![Build status][build] ![Lines of Code][loc]
3+
> _Design By Contract_ for Rust
44
5-
[license]: https://img.shields.io/badge/license-MPL%202.0-blue.svg
6-
[build]: https://gitlab.com/karroffel/contracts/badges/master/pipeline.svg
7-
[loc]: https://tokei.rs/b1/gitlab/karroffel/contracts?category=code
5+
<!-- prettier-ignore-start -->
6+
7+
[![crates.io](https://img.shields.io/crates/v/contracts?label=latest)](https://crates.io/crates/contracts)
8+
[![Documentation](https://docs.rs/contracts/badge.svg?version=0.6.4)](https://docs.rs/contracts/0.6.4)
9+
[![dependency status](https://deps.rs/crate/contracts/0.6.4/status.svg)](https://deps.rs/crate/contracts/0.6.4)
10+
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/contracts.svg)
11+
<br />
12+
[![CI](https://github.com/x52dev/contracts-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/x52dev/contracts-rs/actions/workflows/ci.yml)
13+
[![codecov](https://codecov.io/gh/x52dev/contracts-rs/graph/badge.svg?token=OpYe6I7dj5)](https://codecov.io/gh/x52dev/contracts-rs)
14+
![Version](https://img.shields.io/crates/msrv/contracts.svg)
15+
[![Download](https://img.shields.io/crates/d/contracts.svg)](https://crates.io/crates/contracts)
16+
17+
<!-- prettier-ignore-end -->
818

919
Annotate functions and methods with "contracts", using _invariants_, _pre-conditions_ and _post-conditions_.
1020

src/implementation/codegen.rs

Lines changed: 47 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
use proc_macro2::{Ident, Span, TokenStream};
66
use quote::ToTokens;
7-
use syn::{spanned::Spanned, visit_mut as visitor, Attribute, Expr, ExprCall, ReturnType};
7+
use syn::{
8+
spanned::Spanned,
9+
visit::{visit_return_type, Visit},
10+
visit_mut::{self as visitor, visit_block_mut, visit_expr_mut, VisitMut},
11+
Attribute, Expr, ExprCall, ReturnType, TypeImplTrait,
12+
};
813

914
use crate::implementation::{Contract, ContractMode, ContractType, FuncWithContracts};
1015

@@ -98,7 +103,6 @@ pub(crate) fn extract_old_calls(contracts: &mut [Contract]) -> Vec<OldExpr> {
98103
}
99104

100105
for assertion in &mut contract.assertions {
101-
use visitor::VisitMut;
102106
extractor.visit_expr_mut(assertion);
103107
}
104108
}
@@ -169,11 +173,11 @@ pub(crate) fn generate(
169173
// creates an assertion appropriate for the current mode
170174
let make_assertion = |mode: ContractMode,
171175
ctype: ContractType,
172-
display: proc_macro2::TokenStream,
176+
display: TokenStream,
173177
exec_expr: &Expr,
174178
desc: &str| {
175179
let span = display.span();
176-
let mut result = proc_macro2::TokenStream::new();
180+
let mut result = TokenStream::new();
177181

178182
let format_args = quote::quote_spanned! { span=>
179183
concat!(concat!(#desc, ": "), stringify!(#display))
@@ -208,7 +212,7 @@ pub(crate) fn generate(
208212
// generate assertion code for pre-conditions
209213
//
210214

211-
let pre: proc_macro2::TokenStream = func
215+
let pre = func
212216
.contracts
213217
.iter()
214218
.filter(|c| c.ty == ContractType::Requires || c.ty == ContractType::Invariant)
@@ -222,7 +226,7 @@ pub(crate) fn generate(
222226
let desc = if let Some(desc) = c.desc.as_ref() {
223227
format!("{} of {} violated: {}", contract_type_name, func_name, desc)
224228
} else {
225-
format!("{} of {} violated", c.ty.message_name(), func_name)
229+
format!("{} of {} violated", contract_type_name, func_name)
226230
};
227231

228232
c.assertions
@@ -240,13 +244,13 @@ pub(crate) fn generate(
240244
)
241245
})
242246
})
243-
.collect();
247+
.collect::<TokenStream>();
244248

245249
//
246250
// generate assertion code for post-conditions
247251
//
248252

249-
let post: proc_macro2::TokenStream = func
253+
let post = func
250254
.contracts
251255
.iter()
252256
.filter(|c| c.ty == ContractType::Ensures || c.ty == ContractType::Invariant)
@@ -260,7 +264,7 @@ pub(crate) fn generate(
260264
let desc = if let Some(desc) = c.desc.as_ref() {
261265
format!("{} of {} violated: {}", contract_type_name, func_name, desc)
262266
} else {
263-
format!("{} of {} violated", c.ty.message_name(), func_name)
267+
format!("{} of {} violated", contract_type_name, func_name)
264268
};
265269

266270
c.assertions
@@ -278,14 +282,14 @@ pub(crate) fn generate(
278282
)
279283
})
280284
})
281-
.collect();
285+
.collect::<TokenStream>();
282286

283287
//
284288
// bind "old()" expressions
285289
//
286290

287291
let olds = {
288-
let mut toks = proc_macro2::TokenStream::new();
292+
let mut toks = TokenStream::new();
289293

290294
for old in olds {
291295
let span = old.expr.span();
@@ -305,14 +309,27 @@ pub(crate) fn generate(
305309
};
306310

307311
//
308-
// wrap the function body in a closure if we have any postconditions
312+
// wrap the function body in a block so that we can use its return value
309313
//
310314

311-
let body = if post.is_empty() {
312-
let block = &func.function.block;
313-
quote::quote! { let ret = #block; }
314-
} else {
315-
create_body_closure(&func.function)
315+
let body = 'blk: {
316+
let mut block = func.function.block.clone();
317+
visit_block_mut(&mut ReturnReplacer, &mut block);
318+
319+
let mut impl_detector = ImplDetector { found_impl: false };
320+
visit_return_type(&mut impl_detector, &func.function.sig.output);
321+
322+
if !impl_detector.found_impl {
323+
if let ReturnType::Type(.., ref return_type) = func.function.sig.output {
324+
break 'blk quote::quote! {
325+
let ret: #return_type = 'run: #block;
326+
};
327+
}
328+
}
329+
330+
quote::quote! {
331+
let ret = 'run: #block;
332+
}
316333
};
317334

318335
//
@@ -346,177 +363,25 @@ pub(crate) fn generate(
346363
func.function.into_token_stream()
347364
}
348365

349-
struct SelfReplacer<'a> {
350-
replace_with: &'a syn::Ident,
351-
}
366+
struct ReturnReplacer;
352367

353-
impl syn::visit_mut::VisitMut for SelfReplacer<'_> {
354-
fn visit_ident_mut(&mut self, i: &mut Ident) {
355-
if i == "self" {
356-
*i = self.replace_with.clone();
368+
impl VisitMut for ReturnReplacer {
369+
fn visit_expr_mut(&mut self, node: &mut Expr) {
370+
if let Expr::Return(ret_expr) = node {
371+
let ret_expr_expr = ret_expr.expr.clone();
372+
*node = syn::parse_quote!(break 'run #ret_expr_expr);
357373
}
358-
}
359-
}
360-
361-
fn ty_contains_impl_trait(ty: &syn::Type) -> bool {
362-
use syn::visit::Visit;
363-
364-
struct TyContainsImplTrait {
365-
seen_impl_trait: bool,
366-
}
367374

368-
impl syn::visit::Visit<'_> for TyContainsImplTrait {
369-
fn visit_type_impl_trait(&mut self, _: &syn::TypeImplTrait) {
370-
self.seen_impl_trait = true;
371-
}
375+
visit_expr_mut(self, node);
372376
}
373-
374-
let mut vis = TyContainsImplTrait {
375-
seen_impl_trait: false,
376-
};
377-
vis.visit_type(ty);
378-
vis.seen_impl_trait
379377
}
380378

381-
fn create_body_closure(func: &syn::ItemFn) -> TokenStream {
382-
let is_method = func.sig.receiver().is_some();
383-
384-
// If the function has a receiver (e.g. `self`, `&mut self`, or `self: Box<Self>`) rename it
385-
// to `this` within the closure
386-
387-
let mut block = func.block.clone();
388-
let mut closure_args = vec![];
389-
let mut arg_names = vec![];
390-
391-
if is_method {
392-
// `mixed_site` makes the identifier hygienic so it won't collide with a local variable
393-
// named `this`.
394-
let this_ident = syn::Ident::new("this", Span::mixed_site());
395-
396-
let mut receiver = func.sig.inputs[0].clone();
397-
match receiver {
398-
// self, &self, &mut self
399-
syn::FnArg::Receiver(rcv) => {
400-
// The `Self` type.
401-
let self_ty = Box::new(syn::Type::Path(syn::TypePath {
402-
qself: None,
403-
path: syn::Path::from(syn::Ident::new("Self", rcv.span())),
404-
}));
405-
406-
let ty = if let Some((and_token, ref lifetime)) = rcv.reference {
407-
Box::new(syn::Type::Reference(syn::TypeReference {
408-
and_token,
409-
lifetime: lifetime.clone(),
410-
mutability: rcv.mutability,
411-
elem: self_ty,
412-
}))
413-
} else {
414-
self_ty
415-
};
416-
417-
let pat_mut = if rcv.reference.is_none() {
418-
rcv.mutability
419-
} else {
420-
None
421-
};
422-
423-
// this: [& [mut]] Self
424-
let new_rcv = syn::PatType {
425-
attrs: rcv.attrs.clone(),
426-
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
427-
attrs: vec![],
428-
by_ref: None,
429-
mutability: pat_mut,
430-
ident: this_ident.clone(),
431-
subpat: None,
432-
})),
433-
colon_token: syn::Token![:](rcv.span()),
434-
ty,
435-
};
436-
437-
receiver = syn::FnArg::Typed(new_rcv);
438-
}
439-
440-
// self: Box<Self>
441-
syn::FnArg::Typed(ref mut pat) => {
442-
if let syn::Pat::Ident(ref mut ident) = *pat.pat {
443-
if ident.ident == "self" {
444-
ident.ident = this_ident.clone();
445-
}
446-
}
447-
}
448-
}
449-
450-
closure_args.push(receiver);
451-
452-
match &func.sig.inputs[0] {
453-
syn::FnArg::Receiver(receiver) => {
454-
arg_names.push(syn::Ident::new("self", receiver.self_token.span()));
455-
}
456-
syn::FnArg::Typed(pat) => {
457-
if let syn::Pat::Ident(ident) = &*pat.pat {
458-
arg_names.push(ident.ident.clone());
459-
} else {
460-
// Non-trivial receiver pattern => do not capture
461-
closure_args.pop();
462-
}
463-
}
464-
};
465-
466-
// Replace any references to `self` in the function body with references to `this`.
467-
syn::visit_mut::visit_block_mut(
468-
&mut SelfReplacer {
469-
replace_with: &this_ident,
470-
},
471-
&mut block,
472-
);
473-
}
474-
475-
// Any function arguments of the form `ident: ty` become closure arguments and get passed
476-
// explicitly. More complex ones, e.g. pattern matching like `(a, b): (u32, u32)`, are
477-
// captured instead.
478-
let args = func.sig.inputs.iter().skip(if is_method { 1 } else { 0 });
479-
for arg in args {
480-
match arg {
481-
syn::FnArg::Receiver(_) => unreachable!("Multiple receivers?"),
482-
483-
syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => {
484-
if !ty_contains_impl_trait(ty) {
485-
if let syn::Pat::Ident(ident) = &**pat {
486-
let ident_str = ident.ident.to_string();
487-
488-
// Any function argument identifier starting with '_' signals
489-
// that the binding will not be used.
490-
if !ident_str.starts_with('_') || ident_str.starts_with("__") {
491-
arg_names.push(ident.ident.clone());
492-
closure_args.push(arg.clone());
493-
}
494-
}
495-
}
496-
}
497-
}
498-
}
499-
500-
let ret_ty = match &func.sig.output {
501-
ReturnType::Type(_, ty) => {
502-
let span = ty.span();
503-
match ty.as_ref() {
504-
syn::Type::ImplTrait(_) => quote::quote! {},
505-
ty => quote::quote_spanned! { span=>
506-
-> #ty
507-
},
508-
}
509-
}
510-
ReturnType::Default => quote::quote! {},
511-
};
512-
513-
let closure_args = closure_args.iter();
514-
let arg_names = arg_names.iter();
515-
516-
quote::quote! {
517-
#[allow(unused_mut)]
518-
let mut run = |#(#closure_args),*| #ret_ty #block;
379+
struct ImplDetector {
380+
found_impl: bool,
381+
}
519382

520-
let ret = run(#(#arg_names),*);
383+
impl<'a> Visit<'a> for ImplDetector {
384+
fn visit_type_impl_trait(&mut self, _node: &'a TypeImplTrait) {
385+
self.found_impl = true;
521386
}
522387
}

0 commit comments

Comments
 (0)