verus-lang / verusfmt Goto Github PK
View Code? Open in Web Editor NEWAn Opinionated Formatter for Verus
License: MIT License
An Opinionated Formatter for Verus
License: MIT License
verusfmt
0.2.8 formats:
use vstd::prelude::*;
verus! {
proof fn f() {
let s =
seq![
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
}
} // verus!
This occurs with two lines or more. With only one you get:
proof fn f() {
let s = seq![
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
}
Before:
#[verifier(external_body)] // comment breaks it!
pub exec fn foo(a: usize) -> (b: usize)
requires a > 5
ensures b > 5
{
a + 2
}
After:
#[verifier(external_body)] // comment breaks it!pub exec fn foo(a: usize) -> (b: usize)
requires
a > 5,
ensures
b > 5,
{
a + 2
}
The newline after the inline comment is gone.
verusfmt
accepts multiple files on the command line. However, when one of them contains an error, we only report the line number, without reporting the filename, leaving it ambiguous where the error lies.
From vstd/storage_protocol.rs:
forall|q: P|
#![all_triggers]
P::op(p1, q).inv() ==> P::op(p2, q).inv() && P::op(p1, q).interp().dom().disjoint(b1.dom())
&& P::op(p2, q).interp().dom().disjoint(b2.dom()) && P::op(
p1,
q,
).interp().union_prefer_right(b1) =~= P::op(p2, q).interp().union_prefer_right(b2)
very strange indentation choices here
Input:
verus! {
pub exec fn foo(mut_state: &mut Blah) {
let a = { b(&mut_state.c) };
}
} // verus!
Error:
Error: --> 7:27
|
7 | let a = { b(&mut_state.c) };
| ^---
|
= expected COMMENT, bang_str, colons_str, generic_arg_list, or record_expr_field_list
The error goes away and the code is correctly formatted if I do any of the following:
let a = { b(mut_state.c) };
mut_
: let a = { b(&state.c) };
mut
: let a = { b(&xmut_state.c) };
I would guess this is something to do with the parsing of &mut
? rustfmt
is able to handle the analogous Rust code:
pub fn foo(mut_state: &mut Blah) {
let a = { b(&mut_state) };
}
Consider
verus! { mod foo { fn foo() { } } }
It currently gets formatted to:
verus! {
mod foo {
fn foo() {
}
}
} // verus!
It would be preferable if it were formatted to
verus! {
mod foo {
fn foo() {
}
}
} // verus!
verusfmt seems to want to keep moving macro items further to the right on each run
mod foo {
verus! {
bar! {
baz
}
} // verus!
}
$ cargo run -- temp.rs
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/verusfmt temp.rs`
$ cargo run -- --check temp.rs
Finished dev [unoptimized + debuginfo] target(s) in 0.08s
Running `target/debug/verusfmt --check temp.rs`
0.062335083s ERROR Input found not to be well formatted
--- temp.rs
+++ temp.rs.formatted
@@ -2,8 +2,8 @@
verus! {
bar! {
- baz
- }
+ baz
+ }
} // verus!
}
Error: × invalid formatting
$ cargo run -- temp.rs
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/verusfmt temp.rs`
$ cargo run -- --check temp.rs
Finished dev [unoptimized + debuginfo] target(s) in 0.08s
Running `target/debug/verusfmt --check temp.rs`
0.075480083s ERROR Input found not to be well formatted
--- temp.rs
+++ temp.rs.formatted
@@ -2,8 +2,8 @@
verus! {
bar! {
- baz
- }
+ baz
+ }
} // verus!
}
Error: × invalid formatting
Consider the following code:
verus! {
impl a {
fn b()
//
;
}
}
Formatting this is non-idempotent (it does settle to a steady state in 2 iterations though; the first time around, it just doesn't place enough spaces for some reason)
$ cargo run --release -- test.rs
Finished release [optimized] target(s) in 0.08s
Running `target/release/verusfmt test.rs`
$ cargo run --release -- --check test.rs
Finished release [optimized] target(s) in 0.08s
Running `target/release/verusfmt --check test.rs`
0.045484917s ERROR Input found not to be well formatted
--- original
+++ formatted
@@ -1,7 +1,7 @@
verus! {
impl a {
- fn b()//
+ fn b() //
;
}
Error: invalid formatting
It appears that the rules parsed/processed themselves seem to be the same in the --debug
output:
$ # Remember to reset `test.rs` to the file at the start of this issue
$ cargo run --release -- --debug test.rs
Finished release [optimized] target(s) in 0.07s
Running `target/release/verusfmt --debug test.rs`
0.044931166s INFO Processing rule verus_macro_body
0.044958041s INFO Processing rule item
0.044959375s INFO Processing rule impl
0.044960458s INFO Processing rule impl_str
0.044961666s INFO Processing rule type
0.044962541s INFO Processing rule path_type
0.044963333s INFO Processing rule path
0.044964083s INFO Processing rule path_segment
0.044964958s INFO Processing rule name_ref
0.044966000s INFO Processing rule assoc_item_list
0.044966833s INFO Processing rule assoc_items
0.044967791s INFO Processing rule assoc_item
0.044968583s INFO Processing rule fn
0.044969833s INFO Processing rule fn_str
0.044970750s INFO Processing rule name
0.044971583s INFO Processing rule param_list
0.045028083s INFO Processing rule COMMENT
0.045031583s INFO Processing rule fn_qualifier
0.045032875s INFO Processing rule fn_terminator
0.045033791s INFO Processing rule semi_str
$ cargo run --release -- --debug --check test.rs
Finished release [optimized] target(s) in 0.07s
Running `target/release/verusfmt --debug --check test.rs`
0.044528708s INFO Processing rule verus_macro_body
0.044565125s INFO Processing rule item
0.044566583s INFO Processing rule impl
0.044567833s INFO Processing rule impl_str
0.044569000s INFO Processing rule type
0.044569875s INFO Processing rule path_type
0.044570666s INFO Processing rule path
0.044571500s INFO Processing rule path_segment
0.044572458s INFO Processing rule name_ref
0.044573458s INFO Processing rule assoc_item_list
0.044574291s INFO Processing rule assoc_items
0.044575291s INFO Processing rule assoc_item
0.044576041s INFO Processing rule fn
0.044577333s INFO Processing rule fn_str
0.044578291s INFO Processing rule name
0.044579083s INFO Processing rule param_list
0.044644166s INFO Processing rule COMMENT
0.044647583s INFO Processing rule fn_qualifier
0.044648583s INFO Processing rule fn_terminator
0.044649375s INFO Processing rule semi_str
0.044755708s INFO Found some differences in test.rs
0.044770125s ERROR Input found not to be well formatted
--- original
+++ formatted
@@ -1,7 +1,7 @@
verus! {
impl a {
- fn b()//
+ fn b() //
;
}
Error: invalid formatting
In this program:
verus! {
fn test()
requires i < 0, len > 0,
{
}
} // verus!
We parse the requires clause as the path_expr
i<0,len>
followed by the unexpected token 0
, rather than the intended meaning of two binary expressions, namely i < 0
and len > 0
. I don't immediately see an easy fix.
We seem to have a misparse happening due to precedence of special bulleted ops and quantifiers. Minimized example is:
&&& forall|| f
&&& g
which parses as
- expr > expr_inner > prefix_expr
- triple_and: "&&&"
- expr > expr_inner > quantifier_expr
- forall_str: "forall"
- closure_param_list: "||"
- expr
- expr_inner > path_expr_no_generics > path_no_generics > path_segment_no_generics > name_ref > identifier: "f"
- bin_expr_ops > bin_expr_ops_special > triple_and: "&&&"
- expr > expr_inner > path_expr_no_generics > path_no_generics > path_segment_no_generics > name_ref > identifier: "g"
Instead, the g
should not be considered in the body of the forall
.
Original report by @jialin-li:
verusfmt's interaction with bulleted & and forall looks a little misleading, this is the result post formatting
&&& forall|au|
#![auto]
deallocs.contains(au) ==> self.mini_allocator.can_remove(au)
&&& post == EphemeralState {
mini_allocator: self.mini_allocator.prune(deallocs),
..self
}
&&& new_dv == dv
this is also true for exists
||| exists|cut, addr|
self.is_marshalled_state(dv, allocs, deallocs, cut, addr, post, new_dv)
||| self.is_allocator_fill(dv, allocs, deallocs, post, new_dv)
||| self.is_allocator_prune(dv, allocs, deallocs, post, new_dv)
||| self.is_no_op(dv, allocs, deallocs, post, new_dv)
If we format a properly-formatted file that has Windows newlines, then verusfmt replaces them with Unix newlines (at least on a Unix system (possibly most confusingly with WSL?)); however, this may cause confusion on verusfmt --check
since literally every line will look visually identical in the diff. It would be nice if we can detect this as the reason, and provide a better error message rather than dump the whole file in the -
/+
of the diff.
Core issue hit originally by @jaylorch, likely due to git's handling of newline changes.
This example:
fn get(&'a self) -> (o: &'a V) {
0
}
fails with:
1 | fn get(&'a self) -> (o: &'a V) {
| ^---
|
= expected COMMENT, colon_str, dot_dot_eq_str, dot_dot_str, or pipe_str
but the following work:
fn get(&'a self) -> (o: V) {
0
}
fn get(a:int) -> (o: &'a V) {
0
}
fn get(&'a self, b:int) -> (o: &'a V) {
0
}
And on its own, -> (o: &'a V)
is correctly parsed as a ret_type
.
This would be easier to debug if we could get a partial parse tree out of Pest when it fails.
It's a natural pattern to group lines into logical groupings. However, verusfmt
collapses empty lines.
> cat verusfmt.rs
use vstd::prelude::*;
verus! {
fn f() {
// First stanza.
let a = 1;
let b = a;
// Second stanza.
let c = a;
let d = c;
}
} // verus!
> verusfmt --check verusfmt.rs
0.054808000s ERROR Input found not to be well formatted
--- verusfmt.rs
+++ verusfmt.rs.formatted
@@ -6,7 +6,6 @@
// First stanza.
let a = 1;
let b = a;
-
// Second stanza.
let c = a;
let d = c;
Error: × invalid formatting
Not yet a priority TODO item, but could be a relatively straightforward extra mode
The main parse_and_format
has special handling for top-level comments and items, but this means those items don't benefit from/share code with the main formatting logic in to_doc
. For example, when we process this code:
verus! {
pub type ReplicaId = usize; // $line_count$Trusted$
} // verus!
It gets parsed as a top-level type_alias item followed by a comment that gets treated as a top-level suffix comment in parse_and_format
, which means we don't notice that it's been flagged as an inline comment. We could replicate that logic in parse_and_format
, but I suspect we should instead try to simplify parse_and_format
and rely more consistently on to_doc
.
Currently, a file needs to be parse-able to be able to add a #[verusfmt::skip]
declaration to an item. However, it should be possible to support a file-level #![verusfmt::skip]
declaration that doesn't need any parser support (at least nothing any further than src/verus-minimal.pest
).
This can help unblock new syntax or features in Verus that are being blocked by yet-to-be-added support in verusfmt. However, I am concerned that this might lead to files being accidentally left as unformatted for too long. One mitigation for this is to attempt a parsing check anyways if #![verusfmt::skip]
is found, and if parsing succeeds, then throw in a warning saying that more specific #[verusfmt::skip]
s are preferred. If we're being fancy, we can even suggest locations to insert those in based on the results of --check
, but I think at minimum, an implementation of this should do a "warn if parsing succeeds" check.
CC: @Chris-Hawblitzel who asked if there was a way to skip an entire file
Before:
use vstd::prelude::*;
verus! {
fn foo() {
let x: _ = bar();
}
} // verus!
verusfmt
output:
use vstd::prelude::*;
verus! {
fn foo() {
let x: = bar();
}
} // verus!
The wildcard type annotation on x
is gone.
(This is minimized from a larger example in my codegen where the type annotation is on a tuple, but I want to annotate only one element of the tuple.)
macro_rules! a {
() => {
verus! {
}
}
}
$ cargo run -- -Z idempotency-test foo.rs
Finished dev [unoptimized + debuginfo] target(s) in 0.08s
Running `target/debug/verusfmt -Z idempotency-test foo.rs`
0.115861292s ERROR 😱Formatting found to not be idempotent😱
--- foo.rs.formatted-once
+++ foo.rs.formatted-twice
@@ -1,6 +1,6 @@
macro_rules! a {
() => {
verus! {
- }
+ }
};
}
In some (hopefully rare) cases, the developer has a better idea of how a term should be formatted. For example, here:
fn pow2() {
assert(
pow2(0) == 0x1 &&
pow2(1) == 0x2 &&
pow2(2) == 0x4 &&
pow2(3) == 0x8 &&
pow2(4) == 0x10 &&
pow2(5) == 0x20 &&
pow2(6) == 0x40 &&
...
pow2(32) == 0x100000000 &&
pow2(64) == 0x10000000000000000
) by(compute_only);
}
The formatting is intended to make it clear how the exponents are progressing, whereas verusfmt
tries to fit as many terms as it can on each line. One way to support such cases is via functionality similar to rustfmt's #[rustfmt::skip]
attribute.
verus! {
pub exec fn foo() {
let temp_owl__x23 = { let x: Vec<u8> = mk_vec_u8!(); x };
}
} // verus!
This breaks with the following error:
Error: --> 4:58
|
4 | let temp_owl__x23 = { let x: Vec<u8> = mk_vec_u8!(); x };
| ^---
|
= expected COMMENT, at_str, dot_str, dot_dot_str, dot_dot_eq_str, lbracket_str, question_str, semi_str, as_str, else_str, has_str, is_str, bin_expr_ops, or arg_list
It works fine if I make the mk_vec_u8!()
into a normal function call, like mk_vec_u8()
. Seems like something with the parser/grammar.
On branch pretty
.
Before:
impl OwlSpecSerialize for owlSpec_t {
open spec fn as_seq(self) -> Seq<u8> { serialize_owlSpec_t(self) }
}
After:
impl OwlSpecSerializefor owlSpec_t {
open spec fn as_seq(self) -> Seq<u8> {
serialize_owlSpec_t(self)
}
}
Note the space before the for
keyword is gone.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.