Skip to content

Commit c780c07

Browse files
authored
[rust_verify] feat: #[proof_note("label")] attribute (verus-lang#2158)
- introduce the `#[verifier::proof_note("label")]` attribute - slightly refactor `attributes.rs` to accommodate changes - in particular, extend `AttrTree` to differentiate literals and call-like forms - extend tests minimally with `proof_note` usage examples
1 parent 9297eb0 commit c780c07

2 files changed

Lines changed: 72 additions & 47 deletions

File tree

source/rust_verify/src/attributes.rs

Lines changed: 69 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
11
use crate::util::{err_span, vir_err_span_str};
2-
use rustc_ast::token::{Token, TokenKind};
2+
use rustc_ast::token::{LitKind, TokenKind};
33
use rustc_ast::tokenstream::{TokenStream, TokenTree};
44
use rustc_hir::{AttrArgs, Attribute};
55
use rustc_span::Span;
66
use vir::ast::{AcceptRecursiveType, Mode, TriggerAnnotation, VirErr, VirErrAs};
77

8+
/// The syntax tree of an attribute.
9+
///
10+
/// For example, `#[trigger(42)]` would be encoded as follows:
11+
/// `Fun(span, "trigger", [Lit(LitKind::Integer, "42")])`
812
#[derive(Debug)]
9-
pub(crate) enum AttrTree {
13+
enum AttrTree {
14+
/// Similar to a function call, e.g. `trigger(42)`
1015
Fun(Span, String, Option<Box<[AttrTree]>>),
16+
/// A literal, e.g. `42`, `42.0`, `"forty-two"`, etc.
17+
Lit(LitKind, String),
1118
//Eq(Span, String, String), // TODO(main_new)
1219
}
1320

14-
pub(crate) fn token_to_string(token: &Token) -> Result<Option<String>, ()> {
15-
match token.kind {
16-
TokenKind::Literal(lit) => Ok(Some(lit.symbol.as_str().to_string())),
17-
TokenKind::Ident(symbol, _) => Ok(Some(symbol.as_str().to_string())),
18-
TokenKind::Comma => Ok(None),
19-
_ => Err(()),
20-
}
21-
}
22-
23-
pub(crate) fn token_stream_to_trees(
24-
span: Span,
25-
stream: &TokenStream,
26-
) -> Result<Box<[AttrTree]>, ()> {
21+
fn token_stream_to_trees(span: Span, stream: &TokenStream) -> Result<Box<[AttrTree]>, ()> {
2722
let mut token_trees: Vec<&TokenTree> = Vec::new();
2823
for x in stream.iter() {
2924
// TODO(1.83) trees?
@@ -32,25 +27,30 @@ pub(crate) fn token_stream_to_trees(
3227
let mut i = 0;
3328
let mut trees: Vec<AttrTree> = Vec::new();
3429
while i < token_trees.len() {
35-
match &token_trees[i] {
36-
TokenTree::Token(token, _spacing) => {
37-
if let Some(name) = token_to_string(token)? {
38-
let fargs = if i + 1 < token_trees.len() {
39-
if let TokenTree::Delimited(_, _, _, token_stream) = &token_trees[i + 1] {
40-
i += 1;
41-
Some(token_stream_to_trees(span, token_stream)?)
42-
} else {
43-
None
44-
}
45-
} else {
46-
None
47-
};
48-
trees.push(AttrTree::Fun(span, name, fargs));
49-
}
50-
i += 1;
30+
let TokenTree::Token(token, _spacing) = &token_trees[i] else {
31+
return Err(());
32+
};
33+
match token.kind {
34+
TokenKind::Literal(lit) => {
35+
let text = lit.symbol.as_str().to_string();
36+
trees.push(AttrTree::Lit(lit.kind, text));
37+
}
38+
TokenKind::Ident(symbol, _) => {
39+
let name = symbol.as_str().to_string();
40+
let fargs = if let Some(TokenTree::Delimited(_, _, _, token_stream)) =
41+
&token_trees.get(i + 1)
42+
{
43+
i += 1;
44+
Some(token_stream_to_trees(span, token_stream)?)
45+
} else {
46+
None
47+
};
48+
trees.push(AttrTree::Fun(span, name, fargs));
5149
}
50+
TokenKind::Comma => {}
5251
_ => return Err(()),
5352
}
53+
i += 1;
5454
}
5555
Ok(trees.into_boxed_slice())
5656
}
@@ -260,6 +260,8 @@ pub(crate) enum Attr {
260260
AllowInSpec,
261261
// specify list of places where == is promoted to =~=
262262
AutoExtEqual(vir::ast::AutoExtEqual),
263+
/// Label for a proof obligation, i.e. the attribute `#[verifier::proof_note("label")]`
264+
ProofNote(String),
263265
// add manual trigger to expression inside quantifier
264266
Trigger(Option<Vec<u64>>),
265267
// custom error string to report for precondition failures
@@ -357,7 +359,27 @@ pub(crate) enum Attr {
357359
fn get_trigger_arg(span: Span, attr_tree: &AttrTree) -> Result<u64, VirErr> {
358360
let err_fn = || err_span(span, format!("expected integer constant, found {:?}", &attr_tree));
359361
match attr_tree {
360-
AttrTree::Fun(_, name, None) => name.parse::<u64>().or_else(|_e| err_fn()),
362+
AttrTree::Lit(LitKind::Integer, digits) => digits.parse::<u64>().or_else(|_e| err_fn()),
363+
_ => err_fn(),
364+
}
365+
}
366+
367+
/// Get the `"label"` part out of an attribute like `#[verifier::proof_note("label")]`
368+
fn get_proof_note_label(span: Span, attrs: &Option<Box<[AttrTree]>>) -> Result<&String, VirErr> {
369+
let Some([AttrTree::Lit(LitKind::Str, label)]) = attrs.as_deref() else {
370+
return err_span(span, "expected exactly one argument, a string literal");
371+
};
372+
Ok(label)
373+
}
374+
375+
/// Get the `42` part out of an attribute like `#[rlimit(42)]`
376+
fn get_rlimit_arg(span: Span, attrs: &Option<Box<[AttrTree]>>) -> Result<f32, VirErr> {
377+
let err_fn = || err_span(span, "expected number, or `infinity` for rlimit");
378+
match attrs.as_deref() {
379+
Some([AttrTree::Lit(LitKind::Float | LitKind::Integer, text)]) => {
380+
text.parse::<f32>().or_else(|_| err_fn())
381+
}
382+
Some([AttrTree::Fun(_, text, None)]) if text == "infinity" => Ok(f32::INFINITY),
361383
_ => err_fn(),
362384
}
363385
}
@@ -389,6 +411,10 @@ pub(crate) fn parse_attrs(
389411
}
390412
AttrTree::Fun(_, name, None) if name == "proof" => v.push(Attr::Mode(Mode::Proof)),
391413
AttrTree::Fun(_, name, None) if name == "exec" => v.push(Attr::Mode(Mode::Exec)),
414+
AttrTree::Fun(span, name, attrs) if name == "proof_note" => {
415+
let label = get_proof_note_label(*span, attrs)?;
416+
v.push(Attr::ProofNote(label.clone()))
417+
}
392418
AttrTree::Fun(_, name, None) if name == "trigger" => v.push(Attr::Trigger(None)),
393419
AttrTree::Fun(span, name, Some(args)) if name == "trigger" => {
394420
let mut groups: Vec<u64> = Vec::new();
@@ -487,12 +513,12 @@ pub(crate) fn parse_attrs(
487513
AttrTree::Fun(_, arg, None) if arg == "invariant_block" => {
488514
v.push(Attr::InvariantBlock)
489515
}
490-
AttrTree::Fun(_, arg, Some(box [AttrTree::Fun(_, msg, None)]))
516+
AttrTree::Fun(_, arg, Some(box [AttrTree::Lit(LitKind::Str, msg)]))
491517
if arg == "custom_req_err" =>
492518
{
493519
v.push(Attr::CustomReqErr(msg.clone()))
494520
}
495-
AttrTree::Fun(_, arg, Some(box [AttrTree::Fun(_, msg, None)]))
521+
AttrTree::Fun(_, arg, Some(box [AttrTree::Lit(LitKind::Str, msg)]))
496522
if arg == "custom_err" =>
497523
{
498524
v.push(Attr::CustomErr(msg.clone()))
@@ -587,17 +613,9 @@ pub(crate) fn parse_attrs(
587613
v.push(Attr::AutoExtEqual(auto_ext_equal))
588614
}
589615
AttrTree::Fun(_, arg, None) if arg == "memoize" => v.push(Attr::Memoize),
590-
AttrTree::Fun(span, name, Some(box [AttrTree::Fun(_, r, None)]))
591-
if name == "rlimit" =>
592-
{
593-
let Some(rlimit) = r
594-
.parse::<f32>()
595-
.ok()
596-
.or_else(|| if r == "infinity" { Some(f32::INFINITY) } else { None })
597-
else {
598-
return err_span(*span, "expected number, or `infinity` for rlimit");
599-
};
600-
v.push(Attr::RLimit(rlimit));
616+
AttrTree::Fun(span, name, attrs) if name == "rlimit" => {
617+
let number = get_rlimit_arg(*span, attrs)?;
618+
v.push(Attr::RLimit(number));
601619
}
602620
AttrTree::Fun(_, arg, None) if arg == "truncate" => v.push(Attr::Truncate),
603621
AttrTree::Fun(_, arg, None) if arg == "external_fn_specification" => {
@@ -815,8 +833,9 @@ pub(crate) fn parse_attrs(
815833
},
816834
},
817835
AttrPrefix::Rustc => {
818-
let AttrTree::Fun(span, name, _) = &attr;
819-
v.push(Attr::UnsupportedRustcAttr(name.clone(), *span));
836+
if let AttrTree::Fun(span, name, _) = &attr {
837+
v.push(Attr::UnsupportedRustcAttr(name.clone(), *span));
838+
}
820839
}
821840
}
822841
}
@@ -1306,6 +1325,9 @@ pub(crate) fn get_verifier_attrs_maybe_check(
13061325
Attr::Memoize => vs.memoize = true,
13071326
Attr::RLimit(rlimit) => vs.rlimit = Some(rlimit),
13081327
Attr::Truncate => vs.truncate = true,
1328+
Attr::ProofNote(_) => {
1329+
// TODO: https://github.com/verus-lang/verus/issues/2152
1330+
}
13091331
Attr::UnwrappedBinding => vs.unwrapped_binding = true,
13101332
Attr::Mode(_) => vs.sets_mode = true,
13111333
Attr::InternalRevealFn => vs.internal_reveal_fn = true,

source/rust_verify_test/tests/basic.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ const TEST_REQUIRES1: &str = verus_code_str! {
119119
proof fn test_requires1(a: int, b: int, c: int)
120120
requires
121121
a <= b,
122+
#[verifier::proof_note("Test label #1")]
122123
b <= c,
123124
{
124125
assert(a <= c);
@@ -140,6 +141,7 @@ test_verify_one_file! {
140141
#[test] test_requires3 TEST_REQUIRES1.to_string() + verus_code_str! {
141142
fn test_requires3(a: int, b: int, c: int) {
142143
assume(a <= b);
144+
#[verifier::proof_note("Test label #2")]
143145
assume(b <= c);
144146
proof {
145147
test_requires1(a + a, b + b, c + c);
@@ -154,6 +156,7 @@ const TEST_RET: &str = verus_code_str! {
154156
requires
155157
a <= b,
156158
ensures
159+
#[verifier::proof_note("Test label #3")]
157160
ret <= a + b,
158161
ret <= a + a, // FAILS
159162
ret <= b + b,

0 commit comments

Comments
 (0)