Skip to main content

anodized_core/annotate/
mod.rs

1use syn::{
2    Attribute, Expr, Ident, Meta, Pat, PatIdent,
3    parse::{Parse, ParseStream, Result},
4    parse_quote,
5    spanned::Spanned,
6};
7
8use crate::{Capture, PostCondition, PreCondition, Spec, annotate::syntax::CaptureExpr};
9
10pub mod syntax;
11use syntax::{Captures, Keyword};
12
13#[cfg(test)]
14mod tests;
15
16impl Parse for Spec {
17    fn parse(input: ParseStream) -> Result<Self> {
18        let raw_spec = syntax::SpecArgs::parse(input)?;
19
20        let mut prev_keyword: Option<Keyword> = None;
21        let mut requires: Vec<PreCondition> = vec![];
22        let mut maintains: Vec<PreCondition> = vec![];
23        let mut captures: Vec<Capture> = vec![];
24        let mut binds_pattern: Option<Pat> = None;
25        let mut ensures: Vec<PostCondition> = vec![];
26
27        for arg in raw_spec.args {
28            match &arg.keyword {
29                Keyword::Requires => {
30                    let cfg_attr = find_cfg_attribute(&arg.attrs)?;
31                    let cfg: Option<Meta> = if let Some(attr) = cfg_attr {
32                        Some(attr.parse_args()?)
33                    } else {
34                        None
35                    };
36                    let expr = arg.value.try_into_expr()?;
37                    if let Expr::Array(conditions) = expr {
38                        for expr in conditions.elems {
39                            requires.push(PreCondition {
40                                closure: interpret_expr_as_precondition(expr)?,
41                                cfg: cfg.clone(),
42                            });
43                        }
44                    } else {
45                        requires.push(PreCondition {
46                            closure: interpret_expr_as_precondition(expr)?,
47                            cfg,
48                        });
49                    }
50                }
51                Keyword::Maintains => {
52                    let cfg_attr = find_cfg_attribute(&arg.attrs)?;
53                    let cfg: Option<Meta> = if let Some(attr) = cfg_attr {
54                        Some(attr.parse_args()?)
55                    } else {
56                        None
57                    };
58                    let expr = arg.value.try_into_expr()?;
59                    if let Expr::Array(conditions) = expr {
60                        for expr in conditions.elems {
61                            maintains.push(PreCondition {
62                                closure: interpret_expr_as_precondition(expr)?,
63                                cfg: cfg.clone(),
64                            });
65                        }
66                    } else {
67                        maintains.push(PreCondition {
68                            closure: interpret_expr_as_precondition(expr)?,
69                            cfg,
70                        });
71                    }
72                }
73                Keyword::Captures => {
74                    let cfg_attr = find_cfg_attribute(&arg.attrs)?;
75                    if cfg_attr.is_some() {
76                        return Err(syn::Error::new(
77                            cfg_attr.span(),
78                            "`cfg` attribute is not supported on `captures`",
79                        ));
80                    }
81                    if !captures.is_empty() {
82                        return Err(syn::Error::new(
83                            arg.keyword_span,
84                            "at most one `captures` parameter is allowed; to capture multiple values, use a list: `captures: [expr1, expr2, ...]`",
85                        ));
86                    }
87                    let capture_list = arg.value.try_into_captures()?;
88                    match capture_list {
89                        Captures::One(capture_expr) => {
90                            captures
91                                .push(interpret_capture_expr_as_capture(*capture_expr.clone())?);
92                        }
93                        Captures::Many { elems, .. } => {
94                            for capture_expr in elems {
95                                captures.push(interpret_capture_expr_as_capture(capture_expr)?);
96                            }
97                        }
98                    }
99                }
100                Keyword::Binds => {
101                    let cfg_attr = find_cfg_attribute(&arg.attrs)?;
102                    if cfg_attr.is_some() {
103                        return Err(syn::Error::new(
104                            cfg_attr.span(),
105                            "`cfg` attribute is not supported on `binds`",
106                        ));
107                    }
108                    if binds_pattern.is_some() {
109                        return Err(syn::Error::new(
110                            arg.keyword_span,
111                            "multiple `binds` parameters are not allowed",
112                        ));
113                    }
114                    let pattern = arg.value.try_into_pat()?;
115                    binds_pattern = Some(pattern);
116                }
117                Keyword::Ensures => {
118                    let cfg_attr = find_cfg_attribute(&arg.attrs)?;
119                    let cfg: Option<Meta> = if let Some(attr) = cfg_attr {
120                        Some(attr.parse_args()?)
121                    } else {
122                        None
123                    };
124                    let expr = arg.value.try_into_expr()?;
125                    let default_pattern = binds_pattern.clone().unwrap_or(parse_quote! { output });
126                    if let Expr::Array(conditions) = expr {
127                        for expr in conditions.elems {
128                            ensures.push(PostCondition {
129                                closure: interpret_expr_as_postcondition(
130                                    expr,
131                                    default_pattern.clone(),
132                                )?,
133                                cfg: cfg.clone(),
134                            });
135                        }
136                    } else {
137                        ensures.push(PostCondition {
138                            closure: interpret_expr_as_postcondition(expr, default_pattern)?,
139                            cfg,
140                        });
141                    }
142                }
143                Keyword::Unknown(ident) => {
144                    return Err(syn::Error::new(
145                        arg.keyword_span,
146                        format!("unknown spec keyword `{ident}`"),
147                    ));
148                }
149            }
150
151            if let Some(prev_keyword) = prev_keyword
152                && arg.keyword < prev_keyword
153            {
154                return Err(syn::Error::new(
155                    arg.keyword_span,
156                    "parameters are out of order: their order must be `requires`, `maintains`, `captures`, `binds`, `ensures`",
157                ));
158            }
159            prev_keyword = Some(arg.keyword);
160        }
161
162        Ok(Spec {
163            requires,
164            maintains,
165            captures,
166            ensures,
167            span: input.span(),
168        })
169    }
170}
171
172/// Try to interpret a CaptureExpr as a single Capture
173fn interpret_capture_expr_as_capture(capture_expr: CaptureExpr) -> Result<Capture> {
174    let span = capture_expr.span();
175    let CaptureExpr { expr, as_, pat } = capture_expr;
176
177    match (expr, as_, pat) {
178        // Complete form: <expression> `as` <pattern>
179        (Some(expr), Some(_), Some(pat)) => Ok(Capture { expr, pat }),
180
181        // Shorthand: <identifier>
182        (Some(ref expr @ Expr::Path(ref path)), None, None)
183            if path.path.segments.len() == 1
184                && path.path.leading_colon.is_none()
185                && path.attrs.is_empty()
186                && path.qself.is_none() =>
187        {
188            // auto-generate binding with `old_` prefix
189            let ident = &path.path.segments[0].ident;
190            let ident_alias = Ident::new(&format!("old_{}", ident), ident.span());
191            let pat = Pat::Ident(PatIdent {
192                ident: ident_alias,
193                attrs: vec![],
194                mutability: None,
195                by_ref: None,
196                subpat: None,
197            });
198            Ok(Capture {
199                expr: expr.clone(),
200                pat,
201            })
202        }
203
204        // Missing <pattern>
205        (Some(_), Some(_), None) => {
206            Err(syn::Error::new_spanned(as_, "expected pattern after `as`"))
207        }
208
209        // Missing `as` and <pattern>
210        (Some(expr), None, None) => Err(syn::Error::new_spanned(
211            expr,
212            "complex expression must be bound/descructured: <expression> `as` <pattern>",
213        )),
214
215        // Missing `as`
216        (Some(_), None, Some(pat)) => Err(syn::Error::new_spanned(pat, "expected `as` <pattern>")),
217
218        // Missing <expression>
219        (None, _, _) => Err(syn::Error::new(
220            span,
221            "expected capture: <expression> `as` <pattern>",
222        )),
223    }
224}
225
226/// Interpret expression as a zero-parameter closure, wrapping if necessary.
227/// Used for preconditions which don't need access to the return value.
228fn interpret_expr_as_precondition(expr: Expr) -> Result<syn::ExprClosure> {
229    match expr {
230        // Already a closure, validate it has no arguments.
231        Expr::Closure(closure) => {
232            if closure.inputs.is_empty() {
233                Ok(closure)
234            } else {
235                Err(syn::Error::new_spanned(
236                    closure.or1_token,
237                    format!(
238                        "precondition closure must have no arguments, found {}",
239                        closure.inputs.len()
240                    ),
241                ))
242            }
243        }
244        // Naked expression, wrap in an argumentless closure.
245        expr => Ok(syn::ExprClosure {
246            attrs: vec![],
247            lifetimes: None,
248            constness: None,
249            movability: None,
250            asyncness: None,
251            capture: None,
252            or1_token: Default::default(),
253            inputs: syn::punctuated::Punctuated::new(),
254            or2_token: Default::default(),
255            output: syn::ReturnType::Default,
256            body: Box::new(expr),
257        }),
258    }
259}
260
261/// Interpret expression as a closure with a single argument (eg the list of
262/// aliases and function result), wrapping if necessary.
263/// Used for postconditions which take the return value as an argument.
264fn interpret_expr_as_postcondition(expr: Expr, default_binding: Pat) -> Result<syn::ExprClosure> {
265    match expr {
266        // Already a closure, validate it has exactly one argument.
267        Expr::Closure(closure) => {
268            if closure.inputs.len() == 1 {
269                Ok(closure)
270            } else {
271                Err(syn::Error::new_spanned(
272                    closure.or1_token,
273                    format!(
274                        "postcondition closure must have exactly one argument, found {}",
275                        closure.inputs.len()
276                    ),
277                ))
278            }
279        }
280        // Naked expression, wrap in a closure with default binding.
281        expr => Ok(syn::ExprClosure {
282            attrs: vec![],
283            lifetimes: None,
284            constness: None,
285            movability: None,
286            asyncness: None,
287            capture: None,
288            or1_token: Default::default(),
289            inputs: syn::punctuated::Punctuated::from_iter([default_binding]),
290            or2_token: Default::default(),
291            output: syn::ReturnType::Default,
292            body: Box::new(expr),
293        }),
294    }
295}
296
297fn find_cfg_attribute(attrs: &[Attribute]) -> Result<Option<&Attribute>> {
298    let mut cfg_attr: Option<&Attribute> = None;
299
300    for attr in attrs {
301        if attr.path().is_ident("cfg") {
302            if cfg_attr.is_some() {
303                return Err(syn::Error::new(
304                    attr.span(),
305                    "multiple `cfg` attributes are not supported",
306                ));
307            }
308            cfg_attr = Some(attr);
309        } else {
310            return Err(syn::Error::new(
311                attr.span(),
312                "unsupported attribute; only `cfg` is allowed",
313            ));
314        }
315    }
316
317    Ok(cfg_attr)
318}