Skip to content

Commit 3e3f0d4

Browse files
authored
[anodized-core] feat: remove support for closure-form conditions (#163)
- Remove support for writing a pre/post-condition as an explicit closure. - This relaxes the constraint that the output has to be bound by reference. - Update tests.
1 parent 0cdb952 commit 3e3f0d4

21 files changed

Lines changed: 425 additions & 551 deletions

crates/anodized-core/src/annotate.rs

Lines changed: 7 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use syn::{
22
Attribute, Error, Expr, Ident, Meta, Pat, PatIdent,
33
parse::{Parse, ParseStream, Result},
4-
parse_quote,
54
spanned::Spanned,
65
};
76

@@ -120,7 +119,7 @@ impl Parse for Spec {
120119
}
121120
}
122121
Keyword::Ensures => {
123-
if let Err(error) = arg.parse_postconditions(&binds_pattern, &mut ensures) {
122+
if let Err(error) = arg.parse_postconditions(&mut ensures) {
124123
errors.add(error);
125124
}
126125
}
@@ -152,6 +151,7 @@ impl Parse for Spec {
152151
requires,
153152
maintains,
154153
captures,
154+
binds: binds_pattern,
155155
ensures,
156156
span: input.span(),
157157
})
@@ -295,15 +295,12 @@ impl SpecArg {
295295
if let Expr::Array(conditions) = expr {
296296
for expr in conditions.elems {
297297
preconditions.push(PreCondition {
298-
closure: interpret_expr_as_precondition(expr)?,
298+
expr,
299299
cfg: cfg.clone(),
300300
});
301301
}
302302
} else {
303-
preconditions.push(PreCondition {
304-
closure: interpret_expr_as_precondition(expr)?,
305-
cfg,
306-
});
303+
preconditions.push(PreCondition { expr, cfg });
307304
}
308305
Ok(())
309306
}
@@ -343,31 +340,23 @@ impl SpecArg {
343340
Ok(())
344341
}
345342

346-
fn parse_postconditions(
347-
self,
348-
binds_pattern: &Option<Pat>,
349-
postconditions: &mut Vec<PostCondition>,
350-
) -> Result<()> {
343+
fn parse_postconditions(self, postconditions: &mut Vec<PostCondition>) -> Result<()> {
351344
let cfg_attr = find_cfg_attribute(&self.attrs)?;
352345
let cfg: Option<Meta> = if let Some(attr) = cfg_attr {
353346
Some(attr.parse_args()?)
354347
} else {
355348
None
356349
};
357350
let expr = self.value.try_into_expr()?;
358-
let default_pattern = binds_pattern.clone().unwrap_or(parse_quote! { output });
359351
if let Expr::Array(conditions) = expr {
360352
for expr in conditions.elems {
361353
postconditions.push(PostCondition {
362-
closure: interpret_expr_as_postcondition(expr, default_pattern.clone())?,
354+
expr,
363355
cfg: cfg.clone(),
364356
});
365357
}
366358
} else {
367-
postconditions.push(PostCondition {
368-
closure: interpret_expr_as_postcondition(expr, default_pattern)?,
369-
cfg,
370-
});
359+
postconditions.push(PostCondition { expr, cfg });
371360
}
372361
Ok(())
373362
}
@@ -442,96 +431,6 @@ fn interpret_capture_expr_as_capture(capture_expr: CaptureExpr) -> Result<Captur
442431
}
443432
}
444433

445-
/// Interpret expression as precondition, i.e. a closure that returns `bool` and takes no inputs.
446-
fn interpret_expr_as_precondition(expr: Expr) -> Result<syn::ExprClosure> {
447-
match expr {
448-
// Already a closure.
449-
Expr::Closure(closure) => {
450-
// Ensure it returns `bool`.
451-
let predicate = interpret_closure_as_predicate(closure)?;
452-
// Ensure it takes no inputs.
453-
if predicate.inputs.is_empty() {
454-
Ok(predicate)
455-
} else {
456-
Err(Error::new_spanned(
457-
predicate.or1_token,
458-
format!(
459-
"precondition closure must have no arguments, found {}",
460-
predicate.inputs.len()
461-
),
462-
))
463-
}
464-
}
465-
// Naked expression, wrap in an argumentless closure.
466-
expr => Ok(syn::ExprClosure {
467-
attrs: vec![],
468-
lifetimes: None,
469-
constness: None,
470-
movability: None,
471-
asyncness: None,
472-
capture: None,
473-
or1_token: Default::default(),
474-
inputs: syn::punctuated::Punctuated::new(),
475-
or2_token: Default::default(),
476-
output: parse_quote!(-> bool),
477-
body: Box::new(expr),
478-
}),
479-
}
480-
}
481-
482-
/// Interpret expression as postcondition, i.e. a closure that returns `bool` and takes one input.
483-
fn interpret_expr_as_postcondition(expr: Expr, default_binding: Pat) -> Result<syn::ExprClosure> {
484-
match expr {
485-
// Already a closure.
486-
Expr::Closure(closure) => {
487-
// Ensure it returns `bool`.
488-
let predicate = interpret_closure_as_predicate(closure)?;
489-
// Ensure it takes exactly one input.
490-
if predicate.inputs.len() == 1 {
491-
Ok(predicate)
492-
} else {
493-
Err(Error::new_spanned(
494-
predicate.or1_token,
495-
format!(
496-
"postcondition closure must have exactly one argument, found {}",
497-
predicate.inputs.len()
498-
),
499-
))
500-
}
501-
}
502-
// Naked expression, wrap in a closure with default binding.
503-
expr => Ok(syn::ExprClosure {
504-
attrs: vec![],
505-
lifetimes: None,
506-
constness: None,
507-
movability: None,
508-
asyncness: None,
509-
capture: None,
510-
or1_token: Default::default(),
511-
inputs: syn::punctuated::Punctuated::from_iter([default_binding]),
512-
or2_token: Default::default(),
513-
output: parse_quote!(-> bool),
514-
body: Box::new(expr),
515-
}),
516-
}
517-
}
518-
519-
fn interpret_closure_as_predicate(mut closure: syn::ExprClosure) -> Result<syn::ExprClosure> {
520-
match &closure.output {
521-
syn::ReturnType::Default => {
522-
closure.output = parse_quote!(-> bool);
523-
Ok(closure)
524-
}
525-
syn::ReturnType::Type(_, ty) if matches!(ty.as_ref(), syn::Type::Path(path) if path.qself.is_none() && path.path.is_ident("bool")) => {
526-
Ok(closure)
527-
}
528-
syn::ReturnType::Type(_, ty) => Err(Error::new_spanned(
529-
ty,
530-
"predicate must return `bool`".to_string(),
531-
)),
532-
}
533-
}
534-
535434
fn find_cfg_attribute(attrs: &[Attribute]) -> Result<Option<&Attribute>> {
536435
let mut cfg_attr: Option<&Attribute> = None;
537436

0 commit comments

Comments
 (0)