anodized_core/annotate/
mod.rs

1use proc_macro2::Span;
2use syn::{
3    Attribute, Expr, Ident, Meta, Pat, Token,
4    parse::{Parse, ParseStream, Result},
5    parse_quote,
6    punctuated::Punctuated,
7    spanned::Spanned,
8};
9
10use crate::{Capture, Condition, PostCondition, Spec};
11
12#[cfg(test)]
13mod tests;
14
15impl Parse for Spec {
16    fn parse(input: ParseStream) -> Result<Self> {
17        let args = Punctuated::<SpecArg, Token![,]>::parse_terminated(input)?;
18
19        let mut last_arg_order: Option<ArgOrder> = None;
20        let mut requires: Vec<Condition> = vec![];
21        let mut maintains: Vec<Condition> = vec![];
22        let mut captures: Vec<Capture> = vec![];
23        let mut binds_pattern: Option<Pat> = None;
24        let mut ensures: Vec<PostCondition> = vec![];
25
26        for arg in args {
27            let current_arg_order = arg.get_order();
28            if let Some(last_order) = last_arg_order {
29                if current_arg_order < last_order {
30                    return Err(syn::Error::new(
31                        arg.get_keyword_span(),
32                        "parameters are out of order: their order must be `requires`, `maintains`, `captures`, `binds`, `ensures`",
33                    ));
34                }
35            }
36            last_arg_order = Some(current_arg_order);
37
38            match arg {
39                SpecArg::Requires { cfg, expr, .. } => {
40                    if let Expr::Array(conditions) = expr {
41                        requires.extend(conditions.elems.into_iter().map(|expr| Condition {
42                            expr,
43                            cfg: cfg.clone(),
44                        }));
45                    } else {
46                        requires.push(Condition { expr, cfg });
47                    }
48                }
49                SpecArg::Maintains { cfg, expr, .. } => {
50                    if let Expr::Array(conditions) = expr {
51                        maintains.extend(conditions.elems.into_iter().map(|expr| Condition {
52                            expr,
53                            cfg: cfg.clone(),
54                        }));
55                    } else {
56                        maintains.push(Condition { expr, cfg });
57                    }
58                }
59                SpecArg::Captures { keyword, expr } => {
60                    if !captures.is_empty() {
61                        return Err(syn::Error::new(
62                            keyword.span(),
63                            "at most one `captures` parameter is allowed; to capture multiple values, use a list: `captures: [expr1, expr2, ...]`",
64                        ));
65                    }
66                    if let Expr::Array(array) = expr {
67                        captures.extend(interpret_array_as_captures(array)?);
68                    } else {
69                        captures.push(interpret_expr_as_capture(expr)?);
70                    }
71                }
72                SpecArg::Binds { keyword, pattern } => {
73                    if binds_pattern.is_some() {
74                        return Err(syn::Error::new(
75                            keyword.span(),
76                            "multiple `binds` parameters are not allowed",
77                        ));
78                    }
79                    binds_pattern = Some(pattern);
80                }
81                SpecArg::Ensures { cfg, expr, .. } => {
82                    let default_pattern = binds_pattern.clone().unwrap_or(parse_quote! { output });
83
84                    if let Expr::Array(conditions) = expr {
85                        for expr in conditions.elems {
86                            ensures.push(PostCondition {
87                                closure: interpret_expr_as_postcondition(
88                                    expr,
89                                    default_pattern.clone(),
90                                )?,
91                                cfg: cfg.clone(),
92                            });
93                        }
94                    } else {
95                        ensures.push(PostCondition {
96                            closure: interpret_expr_as_postcondition(expr, default_pattern)?,
97                            cfg,
98                        });
99                    }
100                }
101            }
102        }
103
104        Ok(Spec {
105            requires,
106            maintains,
107            captures,
108            ensures,
109        })
110    }
111}
112
113#[derive(PartialEq, PartialOrd, Clone, Copy, Debug)]
114enum ArgOrder {
115    Requires,
116    Maintains,
117    Captures,
118    Binds,
119    Ensures,
120}
121
122/// An intermediate enum to help parse either a condition or a `binds` setting.
123enum SpecArg {
124    Requires {
125        keyword: kw::requires,
126        cfg: Option<Meta>,
127        expr: Expr,
128    },
129    Ensures {
130        keyword: kw::ensures,
131        cfg: Option<Meta>,
132        expr: Expr,
133    },
134    Maintains {
135        keyword: kw::maintains,
136        cfg: Option<Meta>,
137        expr: Expr,
138    },
139    Captures {
140        keyword: kw::captures,
141        expr: Expr,
142    },
143    Binds {
144        keyword: kw::binds,
145        pattern: Pat,
146    },
147}
148
149impl SpecArg {
150    fn get_order(&self) -> ArgOrder {
151        match self {
152            SpecArg::Requires { .. } => ArgOrder::Requires,
153            SpecArg::Maintains { .. } => ArgOrder::Maintains,
154            SpecArg::Captures { .. } => ArgOrder::Captures,
155            SpecArg::Binds { .. } => ArgOrder::Binds,
156            SpecArg::Ensures { .. } => ArgOrder::Ensures,
157        }
158    }
159
160    fn get_keyword_span(&self) -> Span {
161        match self {
162            SpecArg::Requires { keyword, .. } => keyword.span,
163            SpecArg::Ensures { keyword, .. } => keyword.span,
164            SpecArg::Maintains { keyword, .. } => keyword.span,
165            SpecArg::Captures { keyword, .. } => keyword.span,
166            SpecArg::Binds { keyword, .. } => keyword.span,
167        }
168    }
169}
170
171impl Parse for SpecArg {
172    fn parse(input: ParseStream) -> Result<Self> {
173        let attrs = input.call(Attribute::parse_outer)?;
174        let cfg = parse_cfg_attribute(&attrs)?;
175
176        let lookahead = input.lookahead1();
177        if lookahead.peek(kw::captures) {
178            if cfg.is_some() {
179                return Err(syn::Error::new(
180                    attrs[0].span(),
181                    "`cfg` attribute is not supported on `captures`",
182                ));
183            }
184
185            // Parse `captures: <captures>`
186            let keyword = input.parse::<kw::captures>()?;
187            input.parse::<Token![:]>()?;
188            Ok(SpecArg::Captures {
189                keyword,
190                expr: input.parse()?,
191            })
192        } else if lookahead.peek(kw::binds) {
193            if cfg.is_some() {
194                return Err(syn::Error::new(
195                    attrs[0].span(),
196                    "`cfg` attribute is not supported on `binds`",
197                ));
198            }
199
200            // Parse `binds: <pattern>`
201            let keyword = input.parse::<kw::binds>()?;
202            input.parse::<Token![:]>()?;
203            Ok(SpecArg::Binds {
204                keyword,
205                pattern: Pat::parse_single(input)?,
206            })
207        } else if lookahead.peek(kw::requires) {
208            // Parse `requires: <conditions>`
209            let keyword = input.parse::<kw::requires>()?;
210            input.parse::<Token![:]>()?;
211            Ok(SpecArg::Requires {
212                keyword,
213                cfg,
214                expr: input.parse()?,
215            })
216        } else if lookahead.peek(kw::maintains) {
217            // Parse `maintains: <conditions>`
218            let keyword = input.parse::<kw::maintains>()?;
219            input.parse::<Token![:]>()?;
220            Ok(SpecArg::Maintains {
221                keyword,
222                cfg,
223                expr: input.parse()?,
224            })
225        } else if lookahead.peek(kw::ensures) {
226            // Parse `ensures: <conditions>`
227            let keyword = input.parse::<kw::ensures>()?;
228            input.parse::<Token![:]>()?;
229            Ok(SpecArg::Ensures {
230                keyword,
231                cfg,
232                expr: input.parse()?,
233            })
234        } else {
235            Err(lookahead.error())
236        }
237    }
238}
239
240/// Try to interpret an Expr::Array as a list of Captures
241fn interpret_array_as_captures(array: syn::ExprArray) -> Result<Vec<Capture>> {
242    let mut bindings = Vec::new();
243
244    for elem in array.elems {
245        // Try to interpret each element as a capture
246        // If any fails, propagate that error immediately
247        bindings.push(interpret_expr_as_capture(elem)?);
248    }
249
250    Ok(bindings)
251}
252
253/// Try to interpret an Expr as a single Capture
254fn interpret_expr_as_capture(expr: Expr) -> Result<Capture> {
255    match expr {
256        // Simple identifier: count -> old_count
257        Expr::Path(ref path)
258            if path.path.segments.len() == 1
259                && path.path.leading_colon.is_none()
260                && path.attrs.is_empty()
261                && path.qself.is_none() =>
262        {
263            let ident = &path.path.segments[0].ident;
264            let alias = Ident::new(&format!("old_{}", ident), ident.span());
265            Ok(Capture { expr, alias })
266        }
267        // Cast expression: value as old_value
268        Expr::Cast(cast) => {
269            // The cast.ty should be a simple identifier that we use as the alias
270            if let syn::Type::Path(ref type_path) = *cast.ty {
271                if type_path.path.segments.len() == 1
272                    && type_path.path.leading_colon.is_none()
273                    && type_path.qself.is_none()
274                {
275                    let alias = type_path.path.segments[0].ident.clone();
276                    return Ok(Capture {
277                        expr: *cast.expr,
278                        alias,
279                    });
280                }
281            }
282            Err(syn::Error::new_spanned(
283                cast,
284                "alias must be a simple identifier",
285            ))
286        }
287        // Any other expression requires an explicit alias
288        _ => Err(syn::Error::new_spanned(
289            expr,
290            "complex expressions require an explicit alias using `as`",
291        )),
292    }
293}
294
295fn interpret_expr_as_postcondition(expr: Expr, default_binding: Pat) -> Result<syn::ExprClosure> {
296    match expr {
297        // Already a closure, validate it has exactly one argument.
298        Expr::Closure(closure) => {
299            if closure.inputs.len() == 1 {
300                Ok(closure)
301            } else {
302                Err(syn::Error::new_spanned(
303                    closure.or1_token,
304                    format!(
305                        "postcondition closure must have exactly one argument, found {}",
306                        closure.inputs.len()
307                    ),
308                ))
309            }
310        }
311        // Naked expression, wrap in a closure with default binding.
312        expr => Ok(syn::ExprClosure {
313            attrs: vec![],
314            lifetimes: None,
315            constness: None,
316            movability: None,
317            asyncness: None,
318            capture: None,
319            or1_token: Default::default(),
320            inputs: syn::punctuated::Punctuated::from_iter([default_binding]),
321            or2_token: Default::default(),
322            output: syn::ReturnType::Default,
323            body: Box::new(expr),
324        }),
325    }
326}
327
328fn parse_cfg_attribute(attrs: &[Attribute]) -> Result<Option<Meta>> {
329    let mut cfg_attrs: Vec<Meta> = vec![];
330
331    for attr in attrs {
332        if attr.path().is_ident("cfg") {
333            cfg_attrs.push(attr.parse_args()?);
334        } else {
335            return Err(syn::Error::new(
336                attr.span(),
337                "unsupported attribute; only `cfg` is allowed",
338            ));
339        }
340    }
341
342    if cfg_attrs.len() > 1 {
343        return Err(syn::Error::new(
344            cfg_attrs[1].span(),
345            "multiple `cfg` attributes are not supported",
346        ));
347    }
348
349    Ok(cfg_attrs.pop())
350}
351
352/// Custom keywords for parsing. This allows us to use `requires`, `ensures`, etc.,
353/// as if they were built-in Rust keywords during parsing.
354mod kw {
355    syn::custom_keyword!(requires);
356    syn::custom_keyword!(maintains);
357    syn::custom_keyword!(captures);
358    syn::custom_keyword!(binds);
359    syn::custom_keyword!(ensures);
360}