@@ -2,11 +2,10 @@ use proc_macro::TokenStream;
22use proc_macro2:: { Ident , Span } ;
33use quote:: { ToTokens , quote} ;
44use syn:: {
5- Expr , ItemFn , Pat , Result , ReturnType , Token ,
5+ Expr , ExprClosure , ItemFn , Result , ReturnType , Token ,
66 parse:: { Parse , ParseStream } ,
77 parse_macro_input,
88 punctuated:: Punctuated ,
9- spanned:: Spanned ,
109} ;
1110
1211/// The main procedural macro for defining contracts on functions.
@@ -35,39 +34,31 @@ pub fn contract(args: TokenStream, input: TokenStream) -> TokenStream {
3534
3635/// A container for all parsed arguments from the `#[contract]` attribute.
3736struct ContractArgs {
38- clauses : Vec < Clause > ,
37+ conditions : Vec < Condition > ,
3938 // Optional global rename for the return value.
4039 returns_ident : Option < Ident > ,
4140}
4241
43- /// Represents a single clause, e.g., `requires: x > 0`.
44- struct Clause {
45- flavor : ClauseFlavor ,
46- predicate : Expr ,
47- // Optional per-clause rename for the return value (only for `ensures`).
48- output_binding : Option < Ident > ,
49- }
50-
51- /// The "flavor" of a clause: precondition, postcondition, or invariant.
52- #[ derive( Clone , Copy , PartialEq , Eq ) ]
53- enum ClauseFlavor {
54- Requires ,
55- Ensures ,
56- Maintains ,
42+ /// Represents a single contract condition, e.g., `requires: x > 0`.
43+ enum Condition {
44+ Requires { predicate : Expr } ,
45+ Ensures { predicate : Expr } ,
46+ EnsuresClosure { closure : ExprClosure } ,
47+ Maintains { predicate : Expr } ,
5748}
5849
5950impl Parse for ContractArgs {
6051 /// Custom parser for the contents of `#[contract(...)]`.
6152 fn parse ( input : ParseStream ) -> Result < Self > {
62- let mut clauses = Vec :: new ( ) ;
53+ let mut conditions = Vec :: new ( ) ;
6354 let mut returns_ident = None ;
6455
65- // The arguments are a comma-separated list of clauses or a `returns` key.
56+ // The arguments are a comma-separated list of conditions or a `returns` key.
6657 let items = Punctuated :: < ContractArgItem , Token ! [ , ] > :: parse_terminated ( input) ?;
6758
6859 for item in items {
6960 match item {
70- ContractArgItem :: Clause ( clause ) => clauses . push ( clause ) ,
61+ ContractArgItem :: Condition ( condition ) => conditions . push ( condition ) ,
7162 ContractArgItem :: Returns ( ident) => {
7263 if returns_ident. is_some ( ) {
7364 return Err ( syn:: Error :: new ( ident. span ( ) , "duplicate `returns` key" ) ) ;
@@ -78,15 +69,15 @@ impl Parse for ContractArgs {
7869 }
7970
8071 Ok ( ContractArgs {
81- clauses ,
72+ conditions ,
8273 returns_ident,
8374 } )
8475 }
8576}
8677
87- /// An intermediate enum to help parse either a clause or a `returns` key.
78+ /// An intermediate enum to help parse either a condition or a `returns` key.
8879enum ContractArgItem {
89- Clause ( Clause ) ,
80+ Condition ( Condition ) ,
9081 Returns ( Ident ) ,
9182}
9283
@@ -103,57 +94,38 @@ impl Parse for ContractArgItem {
10394 || lookahead. peek ( kw:: ensures)
10495 || lookahead. peek ( kw:: maintains)
10596 {
106- // Parse a clause like `requires: predicate` or `ensures: |val| predicate`
107- Ok ( ContractArgItem :: Clause ( input. parse ( ) ?) )
97+ // Parse a condition like `requires: predicate` or `ensures: |val| predicate`
98+ Ok ( ContractArgItem :: Condition ( input. parse ( ) ?) )
10899 } else {
109100 Err ( lookahead. error ( ) )
110101 }
111102 }
112103}
113104
114- impl Parse for Clause {
115- /// Parses a single clause .
105+ impl Parse for Condition {
106+ /// Parses a single condition .
116107 fn parse ( input : ParseStream ) -> Result < Self > {
117108 let lookahead = input. lookahead1 ( ) ;
118109 if lookahead. peek ( kw:: requires) {
119110 input. parse :: < kw:: requires > ( ) ?;
120111 input. parse :: < Token ! [ : ] > ( ) ?;
121- Ok ( Clause {
122- flavor : ClauseFlavor :: Requires ,
112+ Ok ( Condition :: Requires {
123113 predicate : input. parse ( ) ?,
124- output_binding : None ,
125114 } )
126115 } else if lookahead. peek ( kw:: ensures) {
127116 input. parse :: < kw:: ensures > ( ) ?;
128117 input. parse :: < Token ! [ : ] > ( ) ?;
129- let mut output_binding = None ;
130- // Check for the optional `|name|` syntax.
131- if input. peek ( Token ! [ |] ) {
132- input. parse :: < Token ! [ |] > ( ) ?;
133- // FIX: Use `Pat::parse_single` instead of `input.parse()`.
134- let pat = Pat :: parse_single ( input) ?;
135- if let Pat :: Ident ( pat_ident) = pat {
136- output_binding = Some ( pat_ident. ident ) ;
137- } else {
138- return Err ( syn:: Error :: new (
139- pat. span ( ) ,
140- "expected a simple identifier for the return value binding" ,
141- ) ) ;
142- }
143- input. parse :: < Token ! [ |] > ( ) ?;
118+ let predicate: Expr = input. parse ( ) ?;
119+ if let Expr :: Closure ( closure) = predicate {
120+ Ok ( Condition :: EnsuresClosure { closure } )
121+ } else {
122+ Ok ( Condition :: Ensures { predicate } )
144123 }
145- Ok ( Clause {
146- flavor : ClauseFlavor :: Ensures ,
147- predicate : input. parse ( ) ?,
148- output_binding,
149- } )
150124 } else if lookahead. peek ( kw:: maintains) {
151125 input. parse :: < kw:: maintains > ( ) ?;
152126 input. parse :: < Token ! [ : ] > ( ) ?;
153- Ok ( Clause {
154- flavor : ClauseFlavor :: Maintains ,
127+ Ok ( Condition :: Maintains {
155128 predicate : input. parse ( ) ?,
156- output_binding : None ,
157129 } )
158130 } else {
159131 Err ( lookahead. error ( ) )
@@ -184,43 +156,15 @@ fn instrument_body(func: &ItemFn, args: &ContractArgs) -> Result<proc_macro2::To
184156 . unwrap_or_else ( || Ident :: new ( "output" , Span :: call_site ( ) ) ) ;
185157
186158 // --- Generate Precondition Checks ---
187- let preconditions = args. clauses . iter ( ) . filter_map ( |c| {
188- if c. flavor == ClauseFlavor :: Requires || c. flavor == ClauseFlavor :: Maintains {
189- let pred = & c. predicate ;
190- let msg = format ! ( "Precondition failed: {}" , pred. to_token_stream( ) ) ;
191- Some ( quote ! { assert!( #pred, #msg) ; } )
192- } else {
193- None
159+ let preconditions = args. conditions . iter ( ) . filter_map ( |c| match c {
160+ Condition :: Requires { predicate } | Condition :: Maintains { predicate } => {
161+ let msg = format ! ( "Precondition failed: {}" , predicate. to_token_stream( ) ) ;
162+ Some ( quote ! { assert!( #predicate, #msg) ; } )
194163 }
164+ _ => None ,
195165 } ) ;
196166
197167 // --- Generate Postcondition Checks ---
198- let postconditions = args. clauses . iter ( ) . filter_map ( |c| {
199- if c. flavor == ClauseFlavor :: Ensures || c. flavor == ClauseFlavor :: Maintains {
200- let pred = & c. predicate ;
201- let msg = format ! ( "Postcondition failed: {}" , pred. to_token_stream( ) ) ;
202-
203- // If the clause has a per-clause `|name|` binding, we create a new
204- // variable with that name that references the global output variable.
205- let maybe_rename = if let Some ( per_clause_ident) = & c. output_binding {
206- quote ! { let #per_clause_ident = & #global_output_ident; }
207- } else {
208- quote ! { }
209- } ;
210-
211- Some ( quote ! {
212- // This block ensures that if we rename the output, it's only for this assertion.
213- {
214- #maybe_rename
215- assert!( #pred, #msg) ;
216- }
217- } )
218- } else {
219- None
220- }
221- } ) ;
222-
223- // --- Construct the New Body ---
224168 let returns_nothing = match & func. sig . output {
225169 ReturnType :: Default => true ,
226170 ReturnType :: Type ( _, ty) => {
@@ -232,6 +176,30 @@ fn instrument_body(func: &ItemFn, args: &ContractArgs) -> Result<proc_macro2::To
232176 }
233177 } ;
234178
179+ let postconditions = args. conditions . iter ( ) . filter_map ( |c| match c {
180+ Condition :: Maintains { predicate } => {
181+ let msg = format ! ( "Postcondition failed: {}" , predicate. to_token_stream( ) ) ;
182+ Some ( quote ! { assert!( #predicate, #msg) ; } )
183+ }
184+ Condition :: Ensures { predicate } => {
185+ if returns_nothing {
186+ return None ;
187+ }
188+ let msg = format ! ( "Postcondition failed: {}" , predicate. to_token_stream( ) ) ;
189+ Some ( quote ! { assert!( ( |#global_output_ident| #predicate) ( #global_output_ident) , #msg) ; } )
190+ }
191+ Condition :: EnsuresClosure { closure } => {
192+ if returns_nothing {
193+ return None ;
194+ }
195+ let msg = format ! ( "Postcondition failed: {}" , closure. to_token_stream( ) ) ;
196+ Some ( quote ! { assert!( ( #closure) ( #global_output_ident) , #msg) ; } )
197+ }
198+ _ => None , // Ignore `requires`
199+ } ) ;
200+
201+ // --- Construct the New Body ---
202+
235203 if returns_nothing {
236204 // Case 1: Function returns `()` or nothing.
237205 if is_async {
0 commit comments