ravencheck_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::parse::Parser;
5use syn::punctuated::Punctuated;
6use syn::ext::IdentExt;
7use syn::{
8    Attribute,
9    Block,
10    FnArg,
11    Ident,
12    Item,
13    ItemFn,
14    ItemMod,
15    Meta,
16    Path,
17    ReturnType,
18    Stmt,
19    Type,
20};
21
22
23#[proc_macro]
24pub fn verify(input: TokenStream) -> TokenStream {
25    // let args: Punctuated<syn::Expr,syn::token::Comma> = syn::parse(input).unwrap();
26    let mut args: Vec<syn::Expr> = syn::parse_macro_input!(input with Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated).into_iter().collect();
27    let e = args.pop().unwrap();
28    let sig = args.pop().unwrap();
29    let out = quote! {
30        (#sig).assert_valid(stringify!(#e));
31    };
32    // println!("{}", out);
33    out.into()
34}
35
36#[derive(Clone, Debug, Eq, PartialEq)]
37enum RvnAttr {
38    Annotate(String),
39    Assume,
40    Declare,
41    Define,
42    Verify,
43}
44
45fn path_to_one_str(p: &Path) -> String {
46    assert!(p.segments.len() == 1);
47    p.segments.first().unwrap().ident.to_string()
48}
49
50impl RvnAttr {
51    fn try_from_attr(attr: &Attribute) -> Option<RvnAttr> {
52        match &attr.meta {
53            Meta::Path(p) if p.segments.len() == 1 => {
54                match path_to_one_str(p).as_str() {
55                    "assume" => Some(RvnAttr::Assume),
56                    "declare" => Some(RvnAttr::Declare),
57                    "define" => Some(RvnAttr::Define),
58                    "verify" => Some(RvnAttr::Verify),
59                    _ => None,
60                }
61            }
62            Meta::List(l) if l.path.segments.len() == 1 => {
63                match path_to_one_str(&l.path).as_str() {
64                    "annotate" => {
65                        let fun_name: Ident = l.parse_args().unwrap();
66                        Some(RvnAttr::Annotate(fun_name.to_string()))
67                    }
68                    _ => None,
69                }
70            }
71            _ => None,
72        }
73    }
74}
75
76fn pop_rvn_attr(attrs: &mut Vec<Attribute>) -> Option<RvnAttr> {
77    let mut out = None;
78    attrs.retain_mut(|attr| {
79        match RvnAttr::try_from_attr(attr) {
80            Some(ra) => {
81                match &out {
82                    Some(other) => panic!(
83                        "only one ravencheck command should be used per item, but both {:?} and {:?} were used together",
84                        other,
85                        ra,
86                    ),
87                    None => {}
88                }
89
90                out = Some(ra);
91
92                // Drop all ravencheck commands
93                false
94            }
95
96            // Don't drop anything that wasn't a ravencheck command
97            None => true, 
98        }
99    });
100
101    out
102}
103
104#[derive(Clone, Debug, Eq, PartialEq)]
105enum SigItem {
106    Annotation(String, Block),
107    Axiom(Block),
108    Goal(Block),
109    Sort(String),
110    UFun(String, Vec<String>, String),
111}
112
113fn handle_top_level(attrs: &mut Vec<Attribute>, sig_items: &mut Vec<SigItem>) {
114    attrs.retain_mut(|attr| {
115        match &attr.meta {
116            Meta::List(l) if l.path.segments.len() == 1 => {
117                match l.path.segments.first().unwrap().ident.to_string().as_str() {
118                    "declare_types" => {
119                        // parse out the type name arg, then push to sig_items
120                        // let types: Ident = l.parse_args().unwrap();
121                        // let types: Punctuated<Ident,Token![,]> = Punctuated::parse_terminated(l.tokens).unwrap();
122
123                        let parser =
124                            Punctuated
125                            ::<Ident,syn::Token![,]>
126                            ::parse_separated_nonempty;
127                        let types = parser
128                            .parse2(l.tokens.clone())
129                            .expect("the #[declare_types(..)] attribute expects one or more type names as arguments");
130
131                        for t in types.iter() {
132                            sig_items.push(SigItem::Sort(t.to_string()));
133                        }
134
135                        // Don't keep
136                        false
137                    }
138
139                    // Otherwise, do keep
140                    _ => true,
141                }
142            }
143
144            // Otherwise, do keep
145            _ => true,
146        }
147    })
148}
149
150fn handle_items(items: &mut Vec<Item>, sig_items: &mut Vec<SigItem>) {
151    items.retain_mut(|item| {
152        match item {
153            Item::Fn(f) => {
154                match pop_rvn_attr(&mut f.attrs) {
155                    Some(RvnAttr::Annotate(fname)) => {
156                        sig_items.push(
157                            SigItem::Annotation(fname, *(f.block).clone())
158                        );
159                        false
160                    }
161                    Some(RvnAttr::Assume) => {
162                        sig_items.push(
163                            SigItem::Axiom(*(f.block).clone())
164                        );
165                        false
166                    }
167                    Some(RvnAttr::Declare) => {
168                        let name = f.sig.ident.to_string();
169                        let mut arg_types = Vec::new();
170                        for arg in f.sig.inputs.iter() {
171                            match arg {
172                                FnArg::Typed(a) => match *(a.ty.clone()) {
173                                    Type::Path(typepath) => {
174                                        arg_types.push(
175                                            typepath.path.segments.first().unwrap().ident.to_string()
176                                        );
177                                    }
178                                    t => todo!("Handle {:?}", t),
179                                }
180                                FnArg::Receiver(_) => panic!("
181you can't use 'declare' on a method function (one that takes a 'self' input)"
182                                ),
183                            }
184                        }
185                        let out_type = match &f.sig.output {
186                            ReturnType::Default => "()".to_string(),
187                            ReturnType::Type(_, t) => match *(t.clone()) {
188                                Type::Path(typepath) => {
189                                    typepath.path.segments.first().unwrap().ident.to_string()
190                                }
191                                t => todo!("Handle {:?}", t),
192                            }
193                        };
194                        sig_items.push(SigItem::UFun(name, arg_types, out_type));
195
196                        true
197                    }
198                    Some(RvnAttr::Define) => todo!("#[define] for fn item"),
199                    Some(RvnAttr::Verify) => {
200                        sig_items.push(
201                            SigItem::Goal(*(f.block).clone())
202                        );
203                        false
204                    }
205                    _ => true,
206                }
207            }
208            Item::Const(i) => {
209                match pop_rvn_attr(&mut i.attrs) {
210                    _ => true,
211                }
212            }
213            Item::Struct(i) => {
214                match pop_rvn_attr(&mut i.attrs) {
215                    Some(RvnAttr::Declare) => {
216                        sig_items.push(SigItem::Sort(i.ident.to_string()));
217                    }
218                    Some(a) => panic!(
219                        "attr {:?} cannot not be used on a struct definition",
220                        a,
221                    ),
222                    None => {}
223                }
224                true
225            }
226            Item::Type(i) => {
227                match pop_rvn_attr(&mut i.attrs) {
228                    Some(RvnAttr::Declare) => {
229                        sig_items.push(SigItem::Sort(i.ident.to_string()));
230                    }
231                    Some(RvnAttr::Define) => todo!("#[define] for type item"),
232                    Some(a) => panic!(
233                        "attr {:?} cannot not be used on a type alias definition",
234                        a,
235                    ),
236                    None => {}
237                }
238                true
239            }
240            _ => true,
241        }
242    });
243}
244
245#[proc_macro_attribute]
246pub fn check_module(attrs: TokenStream, input: TokenStream) -> TokenStream {
247
248    // The macro needs to name the crate that CheckedSig comes from,
249    // and that will be different depending on the context that
250    // check_module is called in.
251    let cratename: Ident = if attrs.is_empty() {
252        Ident::new("ravencheck", Span::call_site())
253    } else {
254        // The parse_any parser is needed since the crate name could
255        // be a normal ident or a keyword (like 'crate' or 'super').
256        let parser = Ident::parse_any;
257        parser.parse(attrs).expect("parse crate name")
258    };
259
260    let mut m: ItemMod = match syn::parse(input).expect("parse input") {
261        Item::Mod(m) => m,
262        i => panic!(
263            "'check_module' macro should only be applied to a module, but it was applied to {:?}",
264            i,
265        ),
266    };
267
268    let mut sig_items: Vec<SigItem> = Vec::new();
269
270    // Handle commands within the top-level attributes
271    handle_top_level(&mut m.attrs, &mut sig_items);
272
273    // Handle per-item commands within the module
274    match &mut m.content {
275        Some((_,items)) => {
276            handle_items(items, &mut sig_items);
277
278            // Turn sig_items into statements (of type syn::Stmt)
279            let stmts: Vec<Stmt> = sig_items.into_iter().map(|i| {
280                match i {
281                    SigItem::Annotation(f,b) => {
282                        let b_tks = format!("{}", quote! { #b });
283                        syn::parse((quote! {
284                            sig.add_annotation(#f, #b_tks);
285                        }).into()).unwrap()
286                    }
287                    SigItem::Axiom(b) => {
288                        let b_tks = format!("{}", quote! { #b });
289                        syn::parse((quote! {
290                            sig.add_axiom(#b_tks);
291                        }).into()).unwrap()
292                    }
293                    SigItem::Goal(b) => {
294                        let b_tks = format!("{}", quote! { #b });
295                        syn::parse((quote! {
296                            sig.assert_valid(#b_tks);
297                        }).into()).unwrap()
298                    }
299                    SigItem::Sort(s) => {
300                        syn::parse((quote! {
301                            sig.add_sort(#s);
302                        }).into()).unwrap()
303                    }
304                    SigItem::UFun(name, inputs, output) => {
305                        let i_tks_v: Vec<String> = inputs.into_iter().map(|i| {
306                            format!("{}", i)
307                        }).collect();
308                        let tks = if output.as_str() == "bool" {
309                            quote! {
310                                sig.add_relation(#name, [#(#i_tks_v),*]);
311                            }
312                        } else {
313                            let o_tks = format!("{}", output);
314                            quote! {
315                                sig.declare_op(#name, [#(#i_tks_v),*], #o_tks);
316                            }
317                        };
318                        syn::parse(tks.into()).unwrap()
319                    }
320                }
321            }).collect();
322            let test: ItemFn = syn::parse((quote! {
323                #[test]
324                fn check_properties() {
325                    let mut sig = CheckedSig::empty();
326
327                    // Interpolate 'stmts' here
328                    #(#stmts)*
329                }
330            }).into()).unwrap();
331
332            let test_s = Item::Fn(test);
333
334            let test_mod = quote! {
335                #[cfg(test)]
336                mod ravencheck_tests {
337                    use #cratename::CheckedSig;
338
339                    #test_s
340                }
341            };
342
343            items.push(syn::parse(test_mod.into()).expect("parse test mod"));
344        }
345        None => {}
346    }
347    let out = quote! {
348        #m
349    };
350    out.into()
351}