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 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 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 false
94 }
95
96 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 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 false
137 }
138
139 _ => true,
141 }
142 }
143
144 _ => 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 let cratename: Ident = if attrs.is_empty() {
252 Ident::new("ravencheck", Span::call_site())
253 } else {
254 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_top_level(&mut m.attrs, &mut sig_items);
272
273 match &mut m.content {
275 Some((_,items)) => {
276 handle_items(items, &mut sig_items);
277
278 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 #(#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}