Skip to content

Commit 775826d

Browse files
Add vstd/contrib and builtin_macros/src/contrib directories (#1813)
* Add vstd/contrib and builtin_macros/src/contrib directories, with contrib/auto_spec example * Document process for adding contrib macros * Fix comment
1 parent 6a85ff1 commit 775826d

8 files changed

Lines changed: 381 additions & 1 deletion

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use verus_syn::visit_mut::VisitMut;
2+
use verus_syn::{Block, Expr, ImplItem, Item, parse_quote_spanned, spanned::Spanned};
3+
4+
struct Visitor;
5+
6+
fn is_verus_proof_stmt(stmt: &verus_syn::Stmt) -> bool {
7+
// remove proof-related macros (similar to dual_spec macro)
8+
use verus_syn::{Expr, ExprUnary, UnOp};
9+
match stmt {
10+
verus_syn::Stmt::Expr(e, _) => match e {
11+
Expr::Assume(..) => true,
12+
Expr::Assert(..) => true,
13+
Expr::AssertForall(..) => true,
14+
Expr::Unary(ExprUnary { op: UnOp::Proof(..), .. }) => true,
15+
_ => false,
16+
},
17+
_ => false,
18+
}
19+
}
20+
21+
impl VisitMut for Visitor {
22+
fn visit_expr_mut(&mut self, expr: &mut Expr) {
23+
verus_syn::visit_mut::visit_expr_mut(self, expr);
24+
use verus_syn::{BinOp, Expr, ExprBinary};
25+
let span = expr.span();
26+
match expr {
27+
Expr::Binary(ExprBinary { op, left, right, .. }) => {
28+
match op {
29+
BinOp::Add(_) => {
30+
*expr = parse_quote_spanned_builtin!(builtin, span => #builtin::add(#left, #right));
31+
}
32+
BinOp::Sub(_) => {
33+
*expr = parse_quote_spanned_builtin!(builtin, span => #builtin::sub(#left, #right));
34+
}
35+
BinOp::Mul(_) => {
36+
*expr = parse_quote_spanned_builtin!(builtin, span => #builtin::mul(#left, #right));
37+
}
38+
_ => {}
39+
}
40+
}
41+
_ => {}
42+
}
43+
}
44+
45+
fn visit_block_mut(&mut self, block: &mut Block) {
46+
block.stmts.retain(|stmt| !is_verus_proof_stmt(stmt));
47+
verus_syn::visit_mut::visit_block_mut(self, block);
48+
}
49+
}
50+
51+
fn auto_spec_fn(
52+
span: proc_macro2::Span,
53+
attrs: &mut Vec<verus_syn::Attribute>,
54+
sig: &mut verus_syn::Signature,
55+
mut block: Block,
56+
) {
57+
attrs.push(parse_quote_spanned!(span => #[verifier::allow_in_spec]));
58+
Visitor.visit_block_mut(&mut block);
59+
sig.spec.returns = Some(parse_quote_spanned!(span => returns (#block)));
60+
}
61+
62+
pub(crate) fn auto_spec_item(
63+
item: &mut Item,
64+
_args: Option<proc_macro2::TokenStream>,
65+
_new_items: &mut Vec<Item>,
66+
) {
67+
if let Item::Fn(f) = item {
68+
let span = f.span();
69+
auto_spec_fn(span, &mut f.attrs, &mut f.sig, *f.block.clone());
70+
}
71+
}
72+
73+
pub(crate) fn auto_spec_impl_item(
74+
item: &mut ImplItem,
75+
_args: Option<proc_macro2::TokenStream>,
76+
_new_items: &mut Vec<ImplItem>,
77+
) {
78+
if let ImplItem::Fn(f) = item {
79+
let span = f.span();
80+
auto_spec_fn(span, &mut f.attrs, &mut f.sig, f.block.clone());
81+
}
82+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
pub mod auto_spec;
2+
3+
use proc_macro2::TokenStream;
4+
use verus_syn::{Attribute, ImplItem, Item, Meta, Path};
5+
6+
fn item_attrs(item: &Item) -> Option<&Vec<Attribute>> {
7+
match item {
8+
Item::Const(i) => Some(&i.attrs),
9+
Item::Enum(i) => Some(&i.attrs),
10+
Item::ExternCrate(i) => Some(&i.attrs),
11+
Item::Fn(i) => Some(&i.attrs),
12+
Item::ForeignMod(i) => Some(&i.attrs),
13+
Item::Impl(i) => Some(&i.attrs),
14+
Item::Macro(i) => Some(&i.attrs),
15+
Item::Mod(i) => Some(&i.attrs),
16+
Item::Static(i) => Some(&i.attrs),
17+
Item::Struct(i) => Some(&i.attrs),
18+
Item::Trait(i) => Some(&i.attrs),
19+
Item::TraitAlias(i) => Some(&i.attrs),
20+
Item::Type(i) => Some(&i.attrs),
21+
Item::Union(i) => Some(&i.attrs),
22+
Item::Use(i) => Some(&i.attrs),
23+
Item::Global(i) => Some(&i.attrs),
24+
Item::BroadcastUse(i) => Some(&i.attrs),
25+
Item::BroadcastGroup(i) => Some(&i.attrs),
26+
Item::AssumeSpecification(i) => Some(&i.attrs),
27+
Item::Verbatim(_) => None,
28+
_ => {
29+
panic!("Item is non_exhaustive, preventing us from catching this panic statically")
30+
}
31+
}
32+
}
33+
34+
fn impl_item_attrs(item: &ImplItem) -> Option<&Vec<Attribute>> {
35+
match item {
36+
ImplItem::Const(i) => Some(&i.attrs),
37+
ImplItem::Fn(i) => Some(&i.attrs),
38+
ImplItem::Type(i) => Some(&i.attrs),
39+
ImplItem::Macro(i) => Some(&i.attrs),
40+
ImplItem::BroadcastGroup(i) => Some(&i.attrs),
41+
ImplItem::Verbatim(_) => None,
42+
_ => {
43+
panic!("ImplItem is non_exhaustive, preventing us from catching this panic statically")
44+
}
45+
}
46+
}
47+
48+
fn traverse_path(path: &Path) -> Option<String> {
49+
if path.leading_colon.is_some() {
50+
return None;
51+
}
52+
let segments: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
53+
let segments: Vec<&str> = segments.iter().map(|s| s.as_str()).collect();
54+
match &segments[..] {
55+
[s] => Some(s.to_string()),
56+
["contrib", s] => Some(s.to_string()),
57+
["vstd", "contrib", s] => Some(s.to_string()),
58+
_ => None,
59+
}
60+
}
61+
62+
fn collect_attrs(attrs: Option<&Vec<Attribute>>) -> Vec<(String, Option<TokenStream>)> {
63+
let Some(attrs) = attrs else {
64+
return vec![];
65+
};
66+
let mut attr_infos: Vec<(String, Option<TokenStream>)> = Vec::new();
67+
for attr in attrs {
68+
let Some(name) = traverse_path(attr.path()) else {
69+
continue;
70+
};
71+
let tokens = match &attr.meta {
72+
Meta::Path(_) => None,
73+
Meta::List(list) => Some(list.tokens.clone()),
74+
Meta::NameValue(_) => None,
75+
};
76+
attr_infos.push((name, tokens));
77+
}
78+
attr_infos
79+
}
80+
81+
// It's often more useful to apply a macro to a verus_syn::Item before it's transformed
82+
// by "verus!" into a verbose syn encoding.
83+
// For example, we may want to look for "proof { ... }" blocks or change the mode of a Fn,
84+
// and it's better to do these directly in verus_syn syntax than to try to reverse engineer
85+
// the syn encoding of these features.
86+
// Therefore, we give contrib macros a chance to preprocess the verus_syn code here.
87+
88+
// Unfortunately, name resolution hasn't run on item's attributes yet,
89+
// so we don't have a good way to identify which macro is which.
90+
// As a hack, we look for any of:
91+
// - #[vstd::contrib::m(args)]
92+
// - #[contrib::m(args)]
93+
// - #[m(args)]
94+
// where m is on the list of contrib macros and (args) is optional.
95+
96+
pub(crate) fn contrib_preprocess_item(item: &mut Item, new_items: &mut Vec<Item>) {
97+
for (name, tokens) in collect_attrs(item_attrs(item)) {
98+
match name.as_str() {
99+
// Add contrib macros needing preprocessing here:
100+
"auto_spec" => auto_spec::auto_spec_item(item, tokens, new_items),
101+
_ => {}
102+
};
103+
}
104+
}
105+
106+
pub(crate) fn contrib_preprocess_impl_item(item: &mut ImplItem, new_items: &mut Vec<ImplItem>) {
107+
for (name, tokens) in collect_attrs(impl_item_attrs(item)) {
108+
match name.as_str() {
109+
// Add contrib macros needing preprocessing here:
110+
"auto_spec" => auto_spec::auto_spec_impl_item(item, tokens, new_items),
111+
_ => {}
112+
}
113+
}
114+
}
115+
116+
pub(crate) fn contrib_preprocess_items(items: &mut Vec<Item>) {
117+
let mut i = 0;
118+
while i < items.len() {
119+
let mut new_items: Vec<Item> = Vec::new();
120+
contrib_preprocess_item(&mut items[i], &mut new_items);
121+
// Add new items and preprocess the new items as well:
122+
items.extend(new_items);
123+
i += 1;
124+
}
125+
}
126+
127+
pub(crate) fn contrib_preprocess_impl_items(items: &mut Vec<ImplItem>) {
128+
let mut i = 0;
129+
while i < items.len() {
130+
let mut new_items: Vec<ImplItem> = Vec::new();
131+
contrib_preprocess_impl_item(&mut items[i], &mut new_items);
132+
// Add new items and preprocess the new items as well:
133+
items.extend(new_items);
134+
i += 1;
135+
}
136+
}

source/builtin_macros/src/lib.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod atomic_ghost;
1616
mod attr_block_trait;
1717
mod attr_rewrite;
1818
mod calc_macro;
19+
mod contrib;
1920
mod enum_synthesize;
2021
mod fndecl;
2122
mod is_variant;
@@ -377,3 +378,48 @@ pub fn proof_decl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
377378
}
378379

379380
/*** End of verus small macro definition for executable items ***/
381+
382+
/*** Start of contrib proc macros
383+
(unfortunately, proc macros must reside at the root of the crate)
384+
385+
To add a contrib proc macro, complete the following steps:
386+
- Add a file in builtin_macros/src/contrib/ that contains the bulk of the macro implementation
387+
(example: builtin_macros/src/contrib/auto_spec.rs)
388+
- Declare the file as a submodule of builtin_macros::contrib by adding "pub mod ..." to the top of
389+
builtin_macros/src/contrib/mod.rs (example: `pub mod auto_spec;`)
390+
- Add a short macro declaration below, calling into your file in builtin_macros/src/contrib
391+
for any complex work (i.e. the macro declaration below should have a body of at most a few lines)
392+
- Add a "pub use" to vstd/contrib/mod.rs (example: `pub use verus_builtin_macros::auto_spec;`)
393+
394+
If your macro needs to manipulate function signatures or function bodies,
395+
it's generally cleaner to write this manipulation on the verus_syn representation of the function
396+
before it is transformed by `verus!`, rather than trying to manipulate the more complicated output
397+
of `verus!`. To work with the verus_syn representation, complete this additional step:
398+
- In builtin_macros/src/contrib/mod.rs,
399+
edit contrib_preprocess_item and/or contrib_preprocess_impl_item to match on your macro name and
400+
call into your code that processes the verus_syn item or impl_item. Example:
401+
`"auto_spec" => auto_spec::auto_spec_item(item, tokens, new_items),`.
402+
Your code can then edit the item/impl_item in place.
403+
It can also optionally emit new items/impl_items by adding them to new_items.
404+
***/
405+
406+
/// This copies the body of an exec function into a "returns" clause,
407+
/// so that the exec function will be also usable as a spec function.
408+
/// For example,
409+
/// `#[vstd::contrib::auto_spec] fn f(u: u8) -> u8 { u / 2 }`
410+
/// becomes:
411+
/// `#[verifier::allow_in_spec] fn f(u: u8) -> u8 returns (u / 2) { u / 2 }`
412+
/// The macro performs some limited fixups, such as removing proof blocks
413+
/// and turning +, -, and * into add, sub, mul.
414+
/// However, only a few such fixups are currently implemented and not all exec bodies
415+
/// will be usable as return clauses, so this macro will not work on all exec functions.
416+
#[proc_macro_attribute]
417+
pub fn auto_spec(
418+
_args: proc_macro::TokenStream,
419+
input: proc_macro::TokenStream,
420+
) -> proc_macro::TokenStream {
421+
// All the work is done in the preprocesssing; this just double-checks name resolution
422+
input
423+
}
424+
425+
/*** End of contrib macros ***/

source/builtin_macros/src/syntax.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,7 @@ impl Visitor {
13731373
}
13741374

13751375
fn visit_items_prefilter(&mut self, items: &mut Vec<Item>) {
1376+
crate::contrib::contrib_preprocess_items(items);
13761377
self.visit_items_make_unerased_proxies(items);
13771378
crate::syntax_trait::expand_extension_traits(self.erase_ghost.erase_all(), items);
13781379

@@ -1847,6 +1848,7 @@ impl Visitor {
18471848
}
18481849

18491850
fn visit_impl_items_prefilter(&mut self, items: &mut Vec<ImplItem>, for_trait: bool) {
1851+
crate::contrib::contrib_preprocess_impl_items(items);
18501852
self.visit_impl_items_make_unerased_proxies(items, for_trait);
18511853

18521854
if self.erase_ghost.erase_all() {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#![feature(rustc_private)]
2+
#[macro_use]
3+
mod common;
4+
use common::*;
5+
6+
// Adapted from auto_spec tests in syntax_attr.rs
7+
test_verify_one_file! {
8+
#[test] test_auto_spec verus_code! {
9+
#[vstd::contrib::auto_spec]
10+
pub fn f(x: u32, y: u32) -> u32
11+
requires
12+
x < 100,
13+
y < 100,
14+
{
15+
proof {
16+
assert(true);
17+
}
18+
x + y
19+
}
20+
21+
#[vstd::contrib::auto_spec]
22+
pub fn f2(x: u32) -> u32
23+
requires
24+
x < 100,
25+
{
26+
f(x, 1)
27+
}
28+
29+
struct S;
30+
31+
impl S {
32+
#[vstd::contrib::auto_spec]
33+
fn foo(&self, x: u32) -> u32 {
34+
x / 2
35+
}
36+
}
37+
38+
proof fn lemma_f(x: u32, y: u32)
39+
requires
40+
x < 100,
41+
ensures
42+
y == 1 ==> f(x, y) == f2(x),
43+
f(x, y) == f(y, x),
44+
f2(x) == f2(x),
45+
f(x, y) == (x + y) as u32,
46+
f2(x) == x + 1,
47+
{}
48+
49+
mod inner {
50+
use super::*;
51+
proof fn lemma_f(x: u32)
52+
requires
53+
x < 100,
54+
ensures
55+
f2(x) == (x + 1),
56+
{}
57+
}
58+
} => Ok(())
59+
}
60+
61+
test_verify_one_file_with_options! {
62+
#[test] test_auto_spec_missing_use ["no-auto-import-verus_builtin"] => verus_code! {
63+
// fails if we don't say "use vstd::contrib::auto_spec;"
64+
#[auto_spec]
65+
fn foo(x: u32) -> u32 {
66+
x / 2
67+
}
68+
} => Err(e) => assert_vir_error_msg(e, "cannot find attribute `auto_spec` in this scope")
69+
}
70+
71+
test_verify_one_file! {
72+
#[test] test_auto_spec_unsupported_body verus_code! {
73+
use vstd::contrib::auto_spec;
74+
#[auto_spec]
75+
fn f(x: &mut u32, y: u32) -> u32
76+
requires
77+
x < 100,
78+
y < 100,
79+
{
80+
*x = *x + y;
81+
*x
82+
}
83+
} => Err(e) => assert_vir_error_msg(e, "The verifier does not yet support the following Rust feature")
84+
}

source/vstd/contrib/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub use verus_builtin_macros::auto_spec;

0 commit comments

Comments
 (0)