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 }
78 Some("for_type") => Some(RvnItemAttr::InstRule(l.tokens.to_string())),
79 _ => None,
80 }
81 }
82 _ => None,
83 }
84 }
85
86 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 => 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 Declare(Item, bool),
126 DeclareType(Ident, usize),
127 Define(Item, bool, bool),
131 Import(ItemUse),
132 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 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 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 let cratename: Ident = if attrs.is_empty() {
436 Ident::new("ravencheck", Span::call_site())
437 } else {
438 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 let mut commands =
454 RccCommand::extract_from_toplevel_attrs(&mut m.attrs);
455 match &mut m.content {
459 Some((_,items)) => {
460
461 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 #(#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 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 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}