ravencheck_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4
5use syn::{
6    Attribute,
7    Ident,
8    Item,
9    ItemFn,
10    ItemMod,
11    ItemUse,
12    Meta,
13    Path,
14    PathArguments,
15    PathSegment,
16    punctuated::Punctuated,
17    Stmt,
18    Token,
19    UseTree,
20};
21use syn::ext::IdentExt;
22use syn::parse::Parser;
23
24use std::collections::VecDeque;
25
26#[derive(Clone, Debug, Eq, PartialEq)]
27enum RvnItemAttr {
28    Annotate(String),
29    AnnotateMulti,
30    Assume,
31    AssumeFor(String),
32    Declare,
33    Define,
34    Falsify,
35    ForCall(String),
36    ForValues(String),
37    Import,
38    InstRule(String),
39    Phantom,
40    Recursive,
41    Verify,
42}
43
44fn path_to_one_str(p: &Path) -> Option<String> {
45    if p.segments.len() == 1 {
46        Some(p.segments.first().unwrap().ident.to_string())
47    } else {
48        None
49    }
50}
51
52impl RvnItemAttr {
53    fn try_from_attr(attr: &Attribute) -> Option<RvnItemAttr> {
54        match &attr.meta {
55            Meta::Path(p) if p.segments.len() == 1 => {
56                match path_to_one_str(p).as_deref() {
57                    Some("annotate") => panic!("#[annotate] needs arguments"),
58                    Some("annotate_multi") => Some(RvnItemAttr::AnnotateMulti),
59                    Some("assume") => Some(RvnItemAttr::Assume),
60                    Some("declare") => Some(RvnItemAttr::Declare),
61                    Some("define") => Some(RvnItemAttr::Define),
62                    Some("define_rec") => panic!("#[define_rec] has been replaced by #[define] followed by #[recursive]"),
63                    Some("falsify") => Some(RvnItemAttr::Falsify),
64                    Some("import") => Some(RvnItemAttr::Import),
65                    Some("phantom") => Some(RvnItemAttr::Phantom),
66                    Some("recursive") => Some(RvnItemAttr::Recursive),
67                    Some("verify") => Some(RvnItemAttr::Verify),
68                    _ => None,
69                }
70            }
71            Meta::List(l) if l.path.segments.len() == 1 => {
72                match path_to_one_str(&l.path).as_deref() {
73                    Some("annotate") =>
74                        Some(RvnItemAttr::Annotate(l.tokens.to_string())),
75                    Some("assume") =>
76                        Some(RvnItemAttr::AssumeFor(l.tokens.to_string())),
77                    Some("assume_for") => {
78                        panic!("#[assume_for(..)] has been replaced by #[assume(..)]")
79                        // let fun_name: Ident = l.parse_args().unwrap();
80                        // Some(RvnAttr::AssumeFor(fun_name.to_string()))
81                    }
82                    Some("for_call") =>
83                        Some(RvnItemAttr::ForCall(l.tokens.to_string())),
84                    Some("for_type") => Some(RvnItemAttr::InstRule(l.tokens.to_string())),
85                    Some("for_values") =>
86                        Some(RvnItemAttr::ForValues(l.tokens.to_string())),
87                    _ => None,
88                }
89            }
90            _ => None,
91        }
92    }
93
94    /// Remove any `RvnAttr`s found from the given `Vec` and return a
95    /// new `Vec` containing them.
96    fn extract_from_attrs(attrs: &mut Vec<Attribute>) -> Vec<RvnItemAttr> {
97        let mut out = Vec::new();
98        attrs.retain_mut(|attr| {
99            match RvnItemAttr::try_from_attr(attr) {
100                Some(a) => {
101                    out.push(a);
102                    false
103                }
104                None => true,
105            }
106        });
107        out
108    }
109
110    fn extract_from_item(item: &mut Item) -> Vec<RvnItemAttr> {
111        match item {
112            Item::Const(i) => Self::extract_from_attrs(&mut i.attrs),
113            Item::Enum(i) => Self::extract_from_attrs(&mut i.attrs),
114            Item::Fn(i) => Self::extract_from_attrs(&mut i.attrs),
115            Item::Impl(_) => Vec::new(),
116            Item::Mod(_) => Vec::new(),
117            Item::Struct(i) => Self::extract_from_attrs(&mut i.attrs),
118            Item::Type(i) => Self::extract_from_attrs(&mut i.attrs),
119            Item::Use(i) => Self::extract_from_attrs(&mut i.attrs),
120            // Assume anything we haven't listed here is something
121            // that we totally ignore.
122            _item => Vec::new(),
123        }
124    }
125}
126
127#[derive(Clone, Debug, Eq, PartialEq)]
128enum RccCommand {
129    Annotate(String, ItemFn),
130    AnnotateMulti(Vec<String>, Vec<String>, ItemFn),
131    Assume(Vec<String>, ItemFn),
132    AssumeFor(String, ItemFn),
133    /// The boolean is `true` if this is a phantom declaration.
134    Declare(Item, bool),
135    DeclareType(Ident, usize),
136    /// The first boolean is `true` if this is a phantome definition,
137    /// and the second boolean is `true` if this is a recursive
138    /// definition.
139    Define(Item, bool, bool),
140    Import(ItemUse),
141    /// The boolean is `true` if this should be verified, and `false`
142    /// if this should be falsified.
143    Goal(bool, ItemFn),
144}
145
146impl RccCommand {
147    fn mk_declare_define(
148        ras: Vec<RvnItemAttr>,
149        item: Item,
150        define: bool,
151    ) -> (Option<Self>, Option<Item>) {
152        let mut phantom = false;
153        let mut recursive = false;
154        for a in ras { match a {
155            RvnItemAttr::Phantom => { phantom = true; },
156            RvnItemAttr::Recursive if define => { recursive = true; },
157            a => panic!(
158                "Unexpected {:?} under #[{}]",
159                a,
160                if define { "define"} else { "declare" },
161            ),
162        }}
163        let ret_item = if !phantom {
164            Some(item.clone())
165        } else {
166            None
167        };
168        if define {
169            (Some(Self::Define(item, phantom, recursive)), ret_item)
170        } else {
171            (Some(Self::Declare(item, phantom)), ret_item)
172        }
173    }
174
175    fn mk_goal(
176        ras: Vec<RvnItemAttr>,
177        item: Item,
178        should_be_valid: bool,
179    ) -> (Option<Self>, Option<Item>) {
180        assert!(
181            ras.len() == 0,
182            "#[verify] and #[falsify] should not have further attributes beneath them",
183        );
184        let item = match item {
185            Item::Fn(i) => i,
186            item => panic!(
187                "should not use #[verify] or #[falsify] on {:?}",
188                item,
189            ),
190        };
191        (Some(Self::Goal(should_be_valid, item)), None)
192    }
193
194    /// Attempt to extract a `RccCommand` from an [`Item`], also
195    /// returning the original (possibly modified) [`Item`] if it
196    /// should remain in the module and be passed along to the Rust
197    /// compiler.
198    fn from_item(mut item: Item) -> (Option<RccCommand>, Option<Item>) {
199        use RvnItemAttr::*;
200
201        let ras = RvnItemAttr::extract_from_item(&mut item);
202
203        // If there are no Ravencheck attrs, return the item
204        // unchanged.
205        if ras.len() == 0 {
206            return (None, Some(item));
207        }
208        let mut ras = VecDeque::from(ras);
209        match ras.pop_front().unwrap() {
210            Annotate(call) => match item {
211                Item::Fn(i) => {
212                    let c = RccCommand::Annotate(call, i);
213                    (Some(c), None)
214                }
215                item => panic!("Can't use #[annotate(..)] on {:?}", item),
216            }
217            AnnotateMulti => match item {
218                Item::Fn(i) => {
219                    let mut call_lines = Vec::new();
220                    let mut value_lines = Vec::new();
221                    for a in ras { match a {
222                        RvnItemAttr::ForCall(c) => { call_lines.push(c); },
223                        RvnItemAttr::ForValues(l) => { value_lines.push(l); },
224                        a => panic!(
225                            "Unexpected {:?} on '{}'",
226                            a,
227                            i.sig.ident
228                        ),
229                    }}
230                    let c = RccCommand::AnnotateMulti(value_lines, call_lines, i);
231                    (Some(c), None)
232                }
233                item => panic!("Can't use #[annotate_multi(..)] on {:?}", item),
234            }
235            Assume => match item {
236                Item::Fn(i) => {
237                    let mut rules = Vec::new();
238                    for a in ras { match a {
239                        RvnItemAttr::InstRule(r) => { rules.push(r); },
240                        a => panic!(
241                            "Unexpected {:?} on '{}'",
242                            a,
243                            i.sig.ident
244                        ),
245                    }}
246                    let c = RccCommand::Assume(rules, i);
247                    (Some(c), None)
248                }
249                item => panic!("Can't use #[assume] on {:?}", item),
250            }
251            AssumeFor(call) => match item {
252                Item::Fn(i) => {
253                    let c = RccCommand::AssumeFor(call, i);
254                    (Some(c), None)
255                }
256                item => panic!("Can't use #[assume(..)] on {:?}", item),
257            }
258            Declare => Self::mk_declare_define(Vec::from(ras), item, false),
259            Define => Self::mk_declare_define(Vec::from(ras), item, true),
260            Falsify => Self::mk_goal(Vec::from(ras), item, false),
261            Import => match item {
262                Item::Use(i) => {
263                    let c = RccCommand::Import(i.clone());
264                    (Some(c), Some(Item::Use(i)))
265                }
266                item => panic!("Can't use #[import] on {:?}", item),
267            }
268            InstRule(_) =>
269                panic!("#[for_type(..)] should only appear under #[assume]"),
270            Phantom =>
271                panic!("#[phantom] should only appear under #[declare] or #[define]"),
272            Verify => Self::mk_goal(Vec::from(ras), item, true),
273            a => todo!("other attrs for from_item: {:?}", a),
274        }
275    }
276
277    fn from_toplevel_attr(attr: &Attribute) -> Vec<Self> {
278        let mut out = Vec::new();
279        match &attr.meta {
280            Meta::List(l) if l.path.segments.len() == 1 => {
281                match path_to_one_str(&l.path).as_deref() {
282                    Some("declare_types") => {
283                        let parser =
284                            Punctuated
285                            ::<Path,syn::Token![,]>
286                            ::parse_separated_nonempty;
287                        let types = parser
288                            .parse2(l.tokens.clone())
289                            .expect("the #[declare_types(..)] attribute expects one or more type names as arguments");
290
291                        for mut p in types.into_iter() {
292                            let seg = p.segments.pop().unwrap().into_value();
293                            let arity = match seg.arguments {
294                                PathArguments::None => 0,
295                                PathArguments::AngleBracketed(a) => a.args.len(),
296                                PathArguments::Parenthesized(..) => panic!("declared types should get angle-bracketed arguments <..>, but {} got parenthesized arguments", seg.ident),
297                            };
298
299                            out.push(RccCommand::DeclareType(seg.ident, arity));
300                        }
301                    }
302                    _ => {}
303                }
304            }
305            _ => {}
306        }
307        out
308    }
309
310    fn extract_from_toplevel_attrs(attrs: &mut Vec<Attribute>) -> Vec<Self> {
311        let mut out = Vec::new();
312        attrs.retain_mut(|attr| {
313            let mut cs = Self::from_toplevel_attr(attr);
314            if cs.len() > 0 {
315                out.append(&mut cs);
316                false
317            } else {
318                true
319            }
320        });
321        out
322    }
323
324    fn extract_from_items(items: &mut Vec<Item>) -> Vec<Self> {
325        let mut commands: Vec<Self> = Vec::new();
326        let mut items_out: Vec<Item> = Vec::new();
327        for item in items.clone() {
328            let (c,i) = Self::from_item(item);
329            if let Some(c) = c {
330                commands.push(c);
331            }
332            if let Some(i) = i {
333                items_out.push(i);
334            }
335        }
336        *items = items_out;
337        commands
338    }
339}
340
341#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
342enum GenMode {
343    Check,
344    Export,
345}
346
347fn generate_stmts(commands: &Vec<RccCommand>, mode: GenMode) -> Vec<Stmt> {
348    let mut out = Vec::new();
349    for c in commands {
350        match c {
351            RccCommand::DeclareType(ident, arity) => {
352                let ident_str = quote!{#ident}.to_string();
353                let s: Stmt = syn::parse2(quote! {
354                    rcc.reg_toplevel_type(#ident_str, #arity);
355                }).unwrap();
356                out.push(s);
357            }
358            RccCommand::Annotate(call_str, item_fn) => {
359                let item_str = quote!{ #item_fn }.to_string();
360                let s: Stmt = syn::parse2(quote! {
361                    rcc.reg_fn_annotate(#call_str, #item_str).unwrap();
362                }).unwrap();
363                out.push(s);
364            }
365            RccCommand::AnnotateMulti(value_strs, call_strs, item_fn) => {
366                let item_str = quote!{ #item_fn }.to_string();
367                let s: Stmt = syn::parse2(quote! {
368                    rcc.reg_fn_annotate_multi(
369                        [#(#value_strs),*],
370                        [#(#call_strs),*],
371                        #item_str
372                    ).unwrap();
373                }).unwrap();
374                out.push(s);
375            }
376            RccCommand::Assume(rules, item_fn) => {
377                let item_str = quote!{ #item_fn }.to_string();
378                let s: Stmt = syn::parse2(quote! {
379                    rcc.reg_fn_assume([#(#rules),*], #item_str);
380                }).unwrap();
381                out.push(s);
382            }
383            RccCommand::AssumeFor(call_str, item_fn) => {
384                let item_str = quote!{ #item_fn }.to_string();
385                let s: Stmt = syn::parse2(quote! {
386                    rcc.reg_fn_assume_for(#call_str, #item_str);
387                }).unwrap();
388                out.push(s);
389            }
390            RccCommand::Declare(item, _is_phantom) => {
391                let item_str = quote!{ #item }.to_string();
392                let s: Stmt = syn::parse2(quote! {
393                    rcc.reg_item_declare(#item_str);
394                }).unwrap();
395                out.push(s)
396            }
397            RccCommand::Define(item, _is_phantom, is_rec) => {
398                let item_str = quote!{ #item }.to_string();
399                let s: Stmt = syn::parse2(quote! {
400                    rcc.reg_item_define(#item_str, #is_rec);
401                }).unwrap();
402                out.push(s)
403            }
404            RccCommand::Import(item) => {
405                let segs = use_tree_to_path(item.tree.clone());
406                let mut punct = Punctuated::<PathSegment, Token![::]>::new();
407                for s in segs {
408                    punct.push(s);
409                }
410                let path = Path { leading_colon: None, segments: punct };
411                let path_str = quote!{ #path }.to_string();
412                let s: Stmt = syn::parse2(quote! {
413                    if rcc.touch_new_path(#path_str) {
414                        #path::ravencheck_exports::apply_exports(&mut rcc);
415                    }
416                }).unwrap();
417                out.push(s);
418            }
419            RccCommand::Goal(should_be_valid, item_fn) => {
420                match mode {
421                    GenMode::Check => {
422                        let item_fn_str = quote!{ #item_fn }.to_string();
423                        let s: Stmt = syn::parse2(quote! {
424                            rcc.reg_fn_goal(#should_be_valid, #item_fn_str);
425                        }).unwrap();
426                        out.push(s);
427                    }
428                    GenMode::Export => {}
429                }
430            }
431        }
432    }
433    out
434}
435
436fn use_tree_to_path(t: UseTree) -> Vec<PathSegment> {
437    match t {
438        UseTree::Path(p) => {
439            let i = p.ident.clone();
440            let mut rest = use_tree_to_path(*p.tree);
441            rest.insert(0, PathSegment{
442                ident: i,
443                arguments: PathArguments::None,
444            });
445            rest
446        },
447        UseTree::Glob(..) => Vec::new(),
448        UseTree::Group(..) => Vec::new(),
449        UseTree::Name(..) => Vec::new(),
450        t => panic!("cannot #[import] use-tree with {:?} node in it", t),
451    }
452}
453
454#[proc_macro_attribute]
455pub fn export_module(attrs: TokenStream, input: TokenStream) -> TokenStream {
456    process_module(attrs, input, true)
457}
458
459#[proc_macro_attribute]
460pub fn check_module(attrs: TokenStream, input: TokenStream) -> TokenStream {
461    process_module(attrs, input, false)
462}
463
464fn process_module(
465    attrs: TokenStream,
466    input: TokenStream,
467    export: bool,
468) -> TokenStream {
469
470    // The macro needs to name the crate that CheckedSig comes from,
471    // and that will be different depending on the context that
472    // check_module is called in.
473    let cratename: Ident = if attrs.is_empty() {
474        Ident::new("ravencheck", Span::call_site())
475    } else {
476        // The parse_any parser is needed since the crate name could
477        // be a normal ident or a keyword (like 'crate' or 'super').
478        let parser = Ident::parse_any;
479        parser.parse(attrs).expect("parse crate name")
480    };
481
482    let mut m: ItemMod = match syn::parse(input).expect("parse input") {
483        Item::Mod(m) => m,
484        i => panic!(
485            "'check_module' macro should only be applied to a module, but it was applied to {:?}",
486            i,
487        ),
488    };
489
490    // Handle commands within the top-level attributes
491    let mut commands =
492        RccCommand::extract_from_toplevel_attrs(&mut m.attrs);
493    // extract_top_level(&mut m.attrs, &mut rcc_items);
494
495    // Handle per-item commands within the module
496    match &mut m.content {
497        Some((_,items)) => {
498            
499            // extract_items(items, &mut rcc_items);
500            commands.append(
501                &mut RccCommand::extract_from_items(items)
502            );
503
504            let mut test_stmts: Vec<Stmt> =
505                generate_stmts(&commands, GenMode::Check);
506            test_stmts.push(
507                syn::parse2(quote!{
508                    rcc.check_goals();
509                }).unwrap()
510            );
511
512            let test: ItemFn = syn::parse((quote! {
513                #[test]
514                fn check_properties() {
515                    let mut rcc = Rcc::new();
516
517                    // Interpolate 'stmts' here
518                    #(#test_stmts)*
519                }
520            }).into()).unwrap();
521
522            let test_s = Item::Fn(test);
523
524            let test_mod = quote! {
525                #[cfg(test)]
526                mod ravencheck_tests {
527                    use #cratename::Rcc;
528
529                    #test_s
530                }
531            };
532
533            // println!("Here is the test module content:");
534            // println!("{}", test_mod);
535
536            items.push(syn::parse(test_mod.into()).expect("parse test mod"));
537
538            if export {
539                let export_stmts: Vec<Stmt> =
540                    generate_stmts(&commands, GenMode::Export);
541
542                let export_mod = quote! {
543                    pub mod ravencheck_exports {
544                        use #cratename::Rcc;
545
546                        pub fn apply_exports(rcc: &mut Rcc) {
547                            #(#export_stmts)*
548                        }
549                    }
550                };
551
552                // println!("Here is the export module content:");
553                // println!("{}", export_mod);
554                items.push(syn::parse(export_mod.into()).expect("parse export mod"));
555            }
556        }
557        None => {}
558    }
559    let out = quote! {
560        #m
561    };
562    out.into()
563}