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