diff --git a/examples/doubly_linked.rs b/examples/doubly_linked.rs index b5bdab1e43..9c170b3f75 100644 --- a/examples/doubly_linked.rs +++ b/examples/doubly_linked.rs @@ -324,7 +324,7 @@ mod doubly_linked_list { } self.ghost_state.borrow_mut().points_to_map.tracked_map_keys_in_place( Map::::new( - |j: nat| 1 <= j && j <= old(self).view().len(), + Set::range(1, old(self)@.len() + 1), |j: nat| (j - 1) as nat, ), ); @@ -406,7 +406,7 @@ mod doubly_linked_list { }; self.ghost_state.borrow_mut().points_to_map.tracked_map_keys_in_place( Map::::new( - |j: nat| 0 <= j && j < old(self).view().len() - 1, + Set::range(0, (old(self)@.len() - 1) as nat), |j: nat| (j + 1) as nat, ), ); diff --git a/examples/doubly_linked_xor.rs b/examples/doubly_linked_xor.rs index 37651f5982..9544ec61f4 100644 --- a/examples/doubly_linked_xor.rs +++ b/examples/doubly_linked_xor.rs @@ -354,7 +354,7 @@ impl DListXor { } (self.perms.borrow_mut()).tracked_map_keys_in_place( Map::::new( - |j: nat| 0 <= j < old(self)@.len() - 1, + Set::range(0, (old(self)@.len() - 1) as nat), |j: nat| (j + 1) as nat, ), ); @@ -423,7 +423,7 @@ impl DListXor { } self.perms.borrow_mut().tracked_map_keys_in_place( Map::::new( - |j: nat| 1 <= j <= old(self)@.len(), + Set::range(1, old(self)@.len() + 1), |j: nat| (j - 1) as nat, ), ); diff --git a/examples/extensionality.rs b/examples/extensionality.rs index d3fd1754b2..389e5c82bf 100644 --- a/examples/extensionality.rs +++ b/examples/extensionality.rs @@ -2,7 +2,7 @@ use verus_builtin::*; use verus_builtin_macros::*; -use vstd::{map::*, seq::*, seq_lib::*, set::*, set_lib::*}; +use vstd::{imap::*, imap_lib::*, iset::*, iset_lib::*, map::*, seq::*, seq_lib::*, set::*, set_lib::*}; verus! { @@ -84,9 +84,9 @@ proof fn assert_maps_equal_with_proof(m: Map, q: Map) } proof fn assert_maps_equal_with_proof2() { - let m = Map::::total(|t: u64| t & 184); - let q = Map::::new(|t: u64| t ^ t == 0, |t: u64| 184 & t); - assert_maps_equal!(m, q, t => { + let m = IMap::::total(|t: u64| t & 184); + let q = IMap::::new(|t: u64| t ^ t == 0, |t: u64| 184 & t); + assert_imaps_equal!(m, q, t => { // show that the `q` map is total: assert_bit_vector(t ^ t == 0); @@ -106,9 +106,9 @@ proof fn test_set(s: Set, t: Set) { } proof fn assert_sets_equal_with_proof() { - let s = Set::::new(|i: u64| i ^ 25 < 100); - let t = Set::::new(|i: u64| 25 ^ i < 100); - assert_sets_equal!(s, t, i => { + let s = ISet::::new(|i: u64| i ^ 25 < 100); + let t = ISet::::new(|i: u64| 25 ^ i < 100); + assert_isets_equal!(s, t, i => { assert_bit_vector(i ^ 25 == 25 ^ i); }); assert(s == t); diff --git a/examples/guide/lib_examples.rs b/examples/guide/lib_examples.rs index 08dbf06afb..b784516cd5 100644 --- a/examples/guide/lib_examples.rs +++ b/examples/guide/lib_examples.rs @@ -13,6 +13,11 @@ proof fn test_seq1() { proof fn test_set1() { let s: Set = set![0, 10, 20, 30, 40]; + assert(s.contains(20)); + assert(s.contains(30)); + assert(!s.contains(60)); + + let s: ISet = iset![0, 10, 20, 30, 40]; assert(s.finite()); assert(s.contains(20)); assert(s.contains(30)); @@ -52,26 +57,35 @@ proof fn test_seq2() { } proof fn test_set2() { - let s: Set = Set::new(|i: int| 0 <= i <= 40 && i % 10 == 0); + let s_finite: Set = Set::range(0, 41).filter(|i: int| i % 10 == 0); + assert(s_finite.contains(20)); + assert(s_finite.contains(30)); + assert(!s_finite.contains(60)); + + let s: ISet = ISet::new(|i: int| 0 <= i <= 40 && i % 10 == 0); assert(s.contains(20)); assert(s.contains(30)); assert(!s.contains(60)); - let s_infinite: Set = Set::new(|i: int| i % 10 == 0); + let s_infinite: ISet = ISet::new(|i: int| i % 10 == 0); assert(s_infinite.contains(20)); assert(s_infinite.contains(30)); assert(!s_infinite.contains(35)); } proof fn test_map2() { - let m: Map = Map::new(|i: int| 0 <= i <= 40 && i % 10 == 0, |i: int| 10 * i); + let m: IMap = IMap::new(|i: int| 0 <= i <= 40 && i % 10 == 0, |i: int| 10 * i); assert(m[20] == 200); assert(m[30] == 300); - let m_infinite: Map = Map::new(|i: int| i % 10 == 0, |i: int| 10 * i); + let m_infinite: IMap = IMap::new(|i: int| i % 10 == 0, |i: int| 10 * i); assert(m_infinite[20] == 200); assert(m_infinite[30] == 300); assert(m_infinite[90] == 900); + + let m_finite: Map = Map::new(Set::range(0, 41), |i: int| 10 * i); + assert(m_finite[20] == 200); + assert(m_finite[30] == 300); } // ANCHOR_END: new @@ -130,8 +144,6 @@ proof fn test_eq2() { /* // ANCHOR: lemma_len_intersect_fail pub proof fn lemma_len_intersect(s1: Set, s2: Set) - requires - s1.finite(), ensures s1.intersect(s2).len() <= s1.len(), decreases @@ -149,8 +161,6 @@ pub proof fn lemma_len_intersect(s1: Set, s2: Set) // ANCHOR: lemma_len_intersect_sketch pub proof fn lemma_len_intersect(s1: Set, s2: Set) - requires - s1.finite(), ensures s1.intersect(s2).len() <= s1.len(), decreases @@ -187,8 +197,6 @@ pub proof fn lemma_len_intersect(s1: Set, s2: Set) // ANCHOR: lemma_len_intersect pub proof fn lemma_len_intersect(s1: Set, s2: Set) - requires - s1.finite(), ensures s1.intersect(s2).len() <= s1.len(), decreases @@ -207,8 +215,6 @@ pub proof fn lemma_len_intersect(s1: Set, s2: Set) // ANCHOR: lemma_len_intersect_commented pub proof fn lemma_len_intersect(s1: Set, s2: Set) - requires - s1.finite(), ensures s1.intersect(s2).len() <= s1.len(), decreases s1.len(), diff --git a/examples/state_machines/dist_rwlock.rs b/examples/state_machines/dist_rwlock.rs index 8846c089a6..da971dbc42 100644 --- a/examples/state_machines/dist_rwlock.rs +++ b/examples/state_machines/dist_rwlock.rs @@ -3,7 +3,7 @@ use verus_builtin::*; use verus_builtin_macros::*; use vstd::atomic_ghost::*; -use vstd::map::*; +use vstd::imap::*; use vstd::multiset::*; use vstd::prelude::*; @@ -23,8 +23,8 @@ tokenized_state_machine!{ #[sharding(variable)] pub exc_locked: bool, - #[sharding(map)] - pub ref_counts: Map, + #[sharding(imap)] + pub ref_counts: IMap, #[sharding(option)] pub exc_pending: Option, @@ -45,7 +45,7 @@ tokenized_state_machine!{ init rc_width = rc_width; init storage = Option::Some(init_t); init exc_locked = false; - init ref_counts = Map::new( + init ref_counts = IMap::new( |i| 0 <= i < rc_width, |i| 0, ); diff --git a/examples/state_machines/flat_combine.rs b/examples/state_machines/flat_combine.rs index 5d9e485d77..15738abbaf 100644 --- a/examples/state_machines/flat_combine.rs +++ b/examples/state_machines/flat_combine.rs @@ -46,20 +46,20 @@ tokenized_state_machine! { #[sharding(constant)] pub num_clients: nat, - #[sharding(map)] - pub clients: Map, + #[sharding(imap)] + pub clients: IMap, - #[sharding(map)] - pub slots: Map, + #[sharding(imap)] + pub slots: IMap, #[sharding(variable)] pub combiner: Combiner, - #[sharding(storage_map)] - pub requests: Map, + #[sharding(storage_imap)] + pub requests: IMap, - #[sharding(storage_map)] - pub responses: Map, + #[sharding(storage_imap)] + pub responses: IMap, } pub open spec fn valid_idx(self, i: nat) -> bool { @@ -169,15 +169,15 @@ tokenized_state_machine! { init!{ initialize(num_clients: nat) { init num_clients = num_clients; - init clients = Map::new( + init clients = IMap::new( |i: nat| 0 <= i && i < num_clients, |i: nat| Client::Idle); - init slots = Map::new( + init slots = IMap::new( |i: nat| 0 <= i && i < num_clients, |i: nat| false); init combiner = Combiner::Collecting { elems: Seq::empty() }; - init requests = Map::empty(); - init responses = Map::empty(); + init requests = IMap::empty(); + init responses = IMap::empty(); } } diff --git a/examples/state_machines/interner.rs b/examples/state_machines/interner.rs index eee13ae6b3..138d50c52b 100644 --- a/examples/state_machines/interner.rs +++ b/examples/state_machines/interner.rs @@ -17,14 +17,14 @@ tokenized_state_machine! {InternSystem { #[sharding(variable)] pub auth: Seq, - #[sharding(persistent_map)] - pub frag: Map, + #[sharding(persistent_imap)] + pub frag: IMap, } init!{ empty() { init auth = Seq::empty(); - init frag = Map::empty(); + init frag = IMap::empty(); } } diff --git a/examples/state_machines/maps.rs b/examples/state_machines/maps.rs index 2af1cb9733..4c9784fe6a 100644 --- a/examples/state_machines/maps.rs +++ b/examples/state_machines/maps.rs @@ -1,7 +1,7 @@ // rust_verify/tests/example.rs #[allow(unused_imports)] use verus_builtin::*; -use vstd::map::*; +use vstd::imap::*; use vstd::{pervasive::*, *}; use verus_state_machines_macros::tokenized_state_machine; @@ -9,14 +9,14 @@ use verus_state_machines_macros::tokenized_state_machine; tokenized_state_machine!( X { fields { - #[sharding(map)] - pub bool_map: Map, + #[sharding(imap)] + pub bool_map: IMap, } init!{ initialize(cond: bool) { - init bool_map = Map::empty().insert(5, true); + init bool_map = IMap::empty().insert(5, true); } } @@ -52,11 +52,11 @@ tokenized_state_machine!( #[sharding(variable)] pub m: int, - #[sharding(map)] - pub map: Map, + #[sharding(imap)] + pub map: IMap, - #[sharding(storage_map)] - pub storage_map: Map, + #[sharding(storage_imap)] + pub storage_map: IMap, } #[invariant] @@ -80,8 +80,8 @@ tokenized_state_machine!( init!{ initialize(cond: bool) { init m = 0; - init storage_map = Map::empty(); - init map = Map::empty(); + init storage_map = IMap::empty(); + init map = IMap::empty(); } } diff --git a/examples/state_machines/top_sort_dfs.rs b/examples/state_machines/top_sort_dfs.rs index d945cff232..f4c69d522c 100644 --- a/examples/state_machines/top_sort_dfs.rs +++ b/examples/state_machines/top_sort_dfs.rs @@ -1,11 +1,11 @@ #![cfg_attr(verus_keep_ghost, verifier::exec_allows_no_decreases_clause)] #![allow(unused_imports)] use verus_state_machines_macros::tokenized_state_machine; -use vstd::map::*; +use vstd::imap::*; +use vstd::iset::*; use vstd::modes::*; use vstd::prelude::*; use vstd::seq::*; -use vstd::set::*; use vstd::slice::*; use vstd::{pervasive::*, prelude::*, *}; @@ -13,12 +13,12 @@ verus! { #[verifier::reject_recursive_types(V)] pub struct DirectedGraph { - pub edges: Set<(V, V)>, + pub edges: ISet<(V, V)>, } impl DirectedGraph { - pub open spec fn dest_set(&self, v: V) -> Set { - Set::new(|w: V| self.edges.contains((v, w))) + pub open spec fn dest_set(&self, v: V) -> ISet { + ISet::new(|w: V| self.edges.contains((v, w))) } pub open spec fn is_sorted(&self, s: Seq) -> bool { @@ -42,11 +42,11 @@ tokenized_state_machine!{ #[sharding(constant)] pub graph: DirectedGraph, - #[sharding(set)] - pub unvisited: Set, + #[sharding(iset)] + pub unvisited: ISet, - #[sharding(persistent_set)] - pub visited: Set, + #[sharding(persistent_iset)] + pub visited: ISet, #[sharding(variable)] pub top_sort: Seq, @@ -55,8 +55,8 @@ tokenized_state_machine!{ init!{ initialize(graph: DirectedGraph) { init graph = graph; - init unvisited = Set::full(); - init visited = Set::empty(); + init unvisited = ISet::full(); + init visited = ISet::empty(); init top_sort = Seq::empty(); } } @@ -73,7 +73,7 @@ tokenized_state_machine!{ } property!{ - done(s: Set) { + done(s: ISet) { have visited >= (s); assert(forall |i| s.contains(i) ==> pre.top_sort.contains(i)); assert(pre.graph.is_sorted(pre.top_sort)); @@ -108,7 +108,7 @@ tokenized_state_machine!{ #[inductive(push_into_top_sort)] fn push_into_top_sort_inductive(pre: Self, post: Self, v: V) { - assert_sets_equal!(post.unvisited, post.visited.complement()); + assert_isets_equal!(post.unvisited, post.visited.complement()); assert forall |a| #[trigger] post.visited.contains(a) implies post.top_sort.contains(a) @@ -147,7 +147,7 @@ impl ConcreteDirectedGraph { spec fn view(&self) -> DirectedGraph { DirectedGraph { - edges: Set::<(usize, usize)>::new( + edges: ISet::<(usize, usize)>::new( |p: (usize, usize)| 0 <= (p.0 as int) < (self.edges@.len() as int) && self.edges@.index( p.0 as int, @@ -531,7 +531,7 @@ fn compute_top_sort(graph: &ConcreteDirectedGraph) -> (tsr: TopSortResult) } let DfsState { top_sort, top_sort_token: Tracked(top_sort_token), .. } = dfs_state; proof { - let ghost s = Set::new(|i: usize| 0 <= i && i < graph.edges@.len()); + let ghost s = ISet::new(|i: usize| 0 <= i && i < graph.edges@.len()); dfs_state.instance.borrow().done(s, &map_visited_deps, &top_sort_token); assert forall|i: usize| 0 <= i && i < graph.edges@.len() implies top_sort@.contains(i) by { assert(s.contains(i)); diff --git a/examples/state_machines/tutorial/fifo.rs b/examples/state_machines/tutorial/fifo.rs index 3401464246..2cd15f7480 100644 --- a/examples/state_machines/tutorial/fifo.rs +++ b/examples/state_machines/tutorial/fifo.rs @@ -45,8 +45,8 @@ tokenized_state_machine!{FifoQueue { // All the stored permissions - #[sharding(storage_map)] - pub storage: Map>, + #[sharding(storage_imap)] + pub storage: IMap>, // Represents the shared `head` field @@ -186,7 +186,7 @@ tokenized_state_machine!{FifoQueue { } init!{ - initialize(backing_cells: Seq, storage: Map>) { + initialize(backing_cells: Seq, storage: IMap>) { // Upon initialization, the user needs to deposit _all_ the relevant // cell permissions to start with. Each permission should indicate // an empty cell. @@ -347,7 +347,7 @@ tokenized_state_machine!{FifoQueue { } #[inductive(initialize)] - fn initialize_inductive(post: Self, backing_cells: Seq, storage: Map>) { + fn initialize_inductive(post: Self, backing_cells: Seq, storage: IMap>) { assert forall|i: nat| 0 <= i && i < post.len() implies post.valid_storage_at_idx(i) by { @@ -535,7 +535,7 @@ pub fn new_queue(len: usize) -> (pc: (Producer, Consumer)) let mut backing_cells_vec = Vec::>::new(); // Initialize map for the permissions to the cells // (keyed by the indices into the vector) - let tracked mut perms = Map::>::tracked_empty(); + let tracked mut perms = IMap::>::tracked_empty(); while backing_cells_vec.len() < len invariant forall|j: nat| diff --git a/examples/summer_school/chapter-6-1.rs b/examples/summer_school/chapter-6-1.rs index dbf1e6bc60..67194f8974 100644 --- a/examples/summer_school/chapter-6-1.rs +++ b/examples/summer_school/chapter-6-1.rs @@ -1,9 +1,8 @@ #![allow(unused_imports)] use verus_builtin::*; use verus_builtin_macros::*; -use vstd::map::*; +use vstd::imap::*; use vstd::seq::*; -use vstd::set::*; use vstd::{pervasive::*, *}; use verus_state_machines_macros::case_on_init; @@ -27,12 +26,12 @@ pub fn default() -> Value { state_machine!{ MapSpec { fields { - pub map: Map, + pub map: IMap, } init!{ empty() { - init map = Map::total(|k| default()); + init map = IMap::total(|k| default()); } } @@ -61,7 +60,7 @@ state_machine!{ // TODO have a way to annotate this as a constant outside of tokenized mode pub map_count: int, - pub maps: Seq>, + pub maps: Seq>, } init!{ @@ -70,9 +69,9 @@ state_machine!{ init map_count = map_count; init maps = Seq::new(map_count as nat, |i| { if i == 0 { - Map::total(|k| default()) + IMap::total(|k| default()) } else { - Map::empty() + IMap::empty() } }); } @@ -125,8 +124,8 @@ state_machine!{ } } - pub open spec fn interp_map(&self) -> Map { - Map::total(|key| self.abstraction_one_key(key)) + pub open spec fn interp_map(&self) -> IMap { + IMap::total(|key| self.abstraction_one_key(key)) } #[invariant] @@ -205,7 +204,7 @@ proof fn next_refines_next_with_macro(pre: ShardedKVProtocol::State, post: Shard { case_on_next!{pre, post, ShardedKVProtocol => { insert(idx, key, value) => { - assert_maps_equal!(pre.interp_map().insert(key, value), post.interp_map(), k => { + assert_imaps_equal!(pre.interp_map().insert(key, value), post.interp_map(), k => { if equal(k, key) { assert(pre.host_has_key(idx, key)); assert(post.host_has_key(idx, key)); @@ -261,7 +260,7 @@ proof fn next_refines_next_with_macro(pre: ShardedKVProtocol::State, post: Shard MapSpec::show::query_op(interp(pre), interp(post), key, value); } transfer(send_idx, recv_idx, key, value) => { - assert_maps_equal!(pre.interp_map(), post.interp_map(), k: Key => { + assert_imaps_equal!(pre.interp_map(), post.interp_map(), k: Key => { if equal(k, key) { assert(pre.host_has_key(send_idx, key)); assert(post.host_has_key(recv_idx, key)); @@ -292,7 +291,7 @@ proof fn init_refines_init_with_macro(post: ShardedKVProtocol::State) { case_on_init!{post, ShardedKVProtocol => { initialize(n) => { - assert_maps_equal!(interp(post).map, Map::total(|k| default()), k: Key => { + assert_imaps_equal!(interp(post).map, IMap::total(|k| default()), k: Key => { assert(interp(post).map.dom().contains(k)); assert(equal(interp(post).map.index(k), default())); }); diff --git a/source/builtin_macros/src/syntax.rs b/source/builtin_macros/src/syntax.rs index cc0ffafc08..b758af0b0f 100644 --- a/source/builtin_macros/src/syntax.rs +++ b/source/builtin_macros/src/syntax.rs @@ -824,9 +824,10 @@ impl Visitor { } InvariantNameSet::Set(InvariantNameSetSet { mut expr }) => { self.visit_expr_mut(&mut expr); + let typ = quote_vstd! { vstd => #vstd::iset::ISet }; spec_stmts.push(Stmt::Expr( Expr::Verbatim( - quote_spanned_builtin!(verus_builtin, expr.span() => #verus_builtin::opens_invariants_set(#expr)), + quote_spanned_builtin!(verus_builtin, expr.span() => #verus_builtin::opens_invariants_set::<#typ>(#expr)), ), Some(Semi { spans: [expr.span()] }), )); diff --git a/source/docs/guide/src/spec_lib.md b/source/docs/guide/src/spec_lib.md index 3422fac15b..2e27952faa 100644 --- a/source/docs/guide/src/spec_lib.md +++ b/source/docs/guide/src/spec_lib.md @@ -12,11 +12,22 @@ returns a length of type `usize`, which is bounded, the `len()` methods of `Seq` and `Set` return lengths of type `nat`, which is unbounded. -Furthermore, `Set` and `Map` can represent infinite sets and maps. -(Sequences, on the other hand, are always finite.) + +`Set` and `Map` represent finite sets and maps. +`ISet` and `IMap` represent possibly-infinite sets and maps. This allows specifications to talk about collections that are larger than could be contained in the physical memory of a computer. +Everything you can do with the finite version can also be done +with the infinite version. One key benefit of the finite version +is that Verus knows the finite property at type time, +which can prevent some SMT-time proof failure surprises. +Another important benefit of the finite version is that you +can use it to define recursive types, e.g., an `enum T` +can contain a field of type `Set`. + +Sequences are always finite. + ## Constructing and using Seq, Set, Map The `seq!`, `set!`, and `map!` macros construct values of type `Seq`, `Set`, and `Map` @@ -25,18 +36,35 @@ with particular contents: ```rust {{#include ../../../../examples/guide/lib_examples.rs:macro}} ``` +The `iset!` and `imap!` macros connstruct finite values of the possibly-infinite +types `ISet` and `IMap`. -The macros above can only construct finite sequences, sets, and maps. -There are also functions `Seq::new`, `Set::new`, and `Map::new`, -which can allocate both finite values and (for sets and maps) infinite values: +The macros above can only construct finite (literal) sequences, sets, and maps. +Sequences can be constructed with `Seq::new`. +Infinite-type sets and maps can be constructed with `ISet::new` and `IMap::new`. + +A finite-typed `Set` can be constructed with `Set::::range(lo, hi)` +or `Set::::full()` for some finite type `A`, then modified as desired with +`Set::map` and `Set::filter`. +One can also be constructed by the `set_build!` macro defined +in the contributed library [set_build.rs](https://github.com/verus-lang/verus/tree/main/source/builtin_macros/src/contrib/set_build.rs) +Finite-typed `Map::new` accepts any `Set` as its domain. ```rust {{#include ../../../../examples/guide/lib_examples.rs:new}} ``` +You can also create a `Set` using `Set::::new(f)`, where +`f: spec_fn(T) -> bool` indicates what values of type `T` are in the set. +But this isn't recommended because it produces an `Option>` +that's `Some` if and only if `ISet::::new(f).finite()` holds. +So it's only useful if you can prove that the set is finite. If you can't +(or don't need to) prove it's finite, just use `ISet::::new(f)` instead. + Each `Map` value has a domain of type `Set` given by `.dom()`. In the `test_map2` example above, `m`'s domain is the finite set `{0, 10, 20, 30, 40}`, while `m_infinite`'s domain is the infinite set `{..., -20, 10, 0, 10, 20, ...}`. +Likewise, each `IMap` has a domain of type `ISet`. For more operations, including sequence contenation (`.add` or `+`), sequence update, @@ -97,7 +125,13 @@ inside `assert`, `ensures`, and `invariant`, so that, for example, `assert(s1 == s2)` actually means `assert(s1 =~= s2)`. See the [Equality via extensionality](extensional_equality.md) section for more details.) -Proofs about set cardinality (`Set::len`) and set finiteness (`Set::finite`) +An `ISet` and a `Set` can never be equal because their types disagree; +should you find yourself needing to relate them, consider the `Set::congruent` +predicate, or convert the `Set` to an `ISet` with `to_iset()` before +checking for equality. (In non-library code, it is best practice to use +exclusively the finite or infinite variant, if feasible.) + +Proofs about set cardinality (`Set::len`) and set finiteness (`ISet::finite`) often require inductive proofs. For example, the exact cardinality of the intersection of two sets depends on which elements the two sets have in common. diff --git a/source/rust_verify/src/externs.rs b/source/rust_verify/src/externs.rs index 737b474ed5..b095c78567 100644 --- a/source/rust_verify/src/externs.rs +++ b/source/rust_verify/src/externs.rs @@ -33,16 +33,23 @@ pub enum VerusExtern { fn verus_builtin_std() -> Box<[(VerusExtern, &'static str, String)]> { vec![ + (VerusExtern::Macros, "builtin_macros", format!("{LIB_PRE}verus_builtin_macros.{LIB_DL}")), ( VerusExtern::Macros, "verus_builtin_macros", format!("{LIB_PRE}verus_builtin_macros.{LIB_DL}"), ), + ( + VerusExtern::Macros, + "state_machines_macros", + format!("{LIB_PRE}verus_state_machines_macros.{LIB_DL}"), + ), ( VerusExtern::Macros, "verus_state_machines_macros", format!("{LIB_PRE}verus_state_machines_macros.{LIB_DL}"), ), + (VerusExtern::Builtin, "builtin", format!("libverus_builtin.rlib")), (VerusExtern::Builtin, "verus_builtin", format!("libverus_builtin.rlib")), (VerusExtern::Vstd, "vstd", format!("libvstd.rlib")), ] diff --git a/source/rust_verify/src/verus_items.rs b/source/rust_verify/src/verus_items.rs index 41ef2041a6..ff456d2bd4 100644 --- a/source/rust_verify/src/verus_items.rs +++ b/source/rust_verify/src/verus_items.rs @@ -341,6 +341,7 @@ pub(crate) enum SetItem { pub(crate) enum VstdItem { SeqFn(vir::interpreter::SeqFn), SetFn(SetItem), + ISetFn(SetItem), Invariant(InvariantItem), ExecNonstaticCall, ProofNonstaticCall, @@ -649,6 +650,13 @@ fn verus_items_map() -> Vec<(&'static str, VerusItem)> { ("verus::vstd::set::Set::insert", VerusItem::Vstd(VstdItem::SetFn(SetItem::Insert), Some(Arc::new("set::Set::insert".to_owned())))), ("verus::vstd::set::Set::remove", VerusItem::Vstd(VstdItem::SetFn(SetItem::Remove), Some(Arc::new("set::Set::remove".to_owned())))), + ("verus::vstd::iset::ISet::empty", VerusItem::Vstd(VstdItem::ISetFn(SetItem::Empty), Some(Arc::new("iset::ISet::empty".to_owned())))), + ("verus::vstd::iset::ISet::full", VerusItem::Vstd(VstdItem::ISetFn(SetItem::Full), Some(Arc::new("iset::ISet::full".to_owned())))), + ("verus::vstd::iset::ISet::contains", VerusItem::Vstd(VstdItem::ISetFn(SetItem::Contains), Some(Arc::new("iset::ISet::contains".to_owned())))), + ("verus::vstd::iset::ISet::subset_of", VerusItem::Vstd(VstdItem::ISetFn(SetItem::SubsetOf), Some(Arc::new("iset::ISet::subset_of".to_owned())))), + ("verus::vstd::iset::ISet::insert", VerusItem::Vstd(VstdItem::ISetFn(SetItem::Insert), Some(Arc::new("iset::ISet::insert".to_owned())))), + ("verus::vstd::iset::ISet::remove", VerusItem::Vstd(VstdItem::ISetFn(SetItem::Remove), Some(Arc::new("iset::ISet::remove".to_owned())))), + ("verus::vstd::invariant::AtomicInvariant::namespace", VerusItem::Vstd(VstdItem::Invariant(InvariantItem::AtomicInvariantNamespace ), Some(Arc::new("invariant::AtomicInvariant::namespace" .to_owned())))), ("verus::vstd::invariant::AtomicInvariant::inv", VerusItem::Vstd(VstdItem::Invariant(InvariantItem::AtomicInvariantInv ), Some(Arc::new("invariant::AtomicInvariant::inv" .to_owned())))), ("verus::vstd::invariant::LocalInvariant::namespace", VerusItem::Vstd(VstdItem::Invariant(InvariantItem::LocalInvariantNamespace ), Some(Arc::new("invariant::LocalInvariant::namespace" .to_owned())))), diff --git a/source/rust_verify_test/tests/arrays.rs b/source/rust_verify_test/tests/arrays.rs index 4f631fa700..af79e9700b 100644 --- a/source/rust_verify_test/tests/arrays.rs +++ b/source/rust_verify_test/tests/arrays.rs @@ -234,10 +234,10 @@ test_verify_one_file! { test_verify_one_file! { #[test] test_recursion_checks_1 verus_code! { use vstd::array::*; - use vstd::map::*; + use vstd::imap::*; struct Foo { - field: [ Map ; 20 ], + field: [ IMap ; 20 ], } } => Err(err) => assert_vir_error_msg(err, "non-positive position") diff --git a/source/rust_verify_test/tests/btree.rs b/source/rust_verify_test/tests/btree.rs index 6a088799bb..1b428a534d 100644 --- a/source/rust_verify_test/tests/btree.rs +++ b/source/rust_verify_test/tests/btree.rs @@ -386,16 +386,22 @@ test_verify_one_file_with_options! { use std::collections::BTreeMap; use std::collections::btree_map::Iter; use vstd::std_specs::btree::*; - use vstd::set::set; + use vstd::set::{set, Set}; use vstd::map::Map; use vstd::string::View; fn test() { + broadcast use vstd::map::group_map_lemmas; + broadcast use vstd::map_lib::group_map_extra; + broadcast use vstd::set_lib::group_set_lib_default; + let mut m = BTreeMap::::new(); assert(m@ == Map::::empty()); m.insert(3, 4); m.insert(6, -8); + assert(m@.dom().contains(3u32)); + assert(m@.dom().contains(6u32)); let mut idx = 0; for (k, v) in iter: m.iter() diff --git a/source/rust_verify_test/tests/hash.rs b/source/rust_verify_test/tests/hash.rs index 1c49061985..037813148d 100644 --- a/source/rust_verify_test/tests/hash.rs +++ b/source/rust_verify_test/tests/hash.rs @@ -890,6 +890,8 @@ test_verify_one_file_with_options! { m.insert(3, 4); m.insert(6, -8); + assert(m@.contains_pair(3u32, 4i8)); + assert(m@.contains_pair(6u32, -8i8)); let mut idx = 0; for (k, v) in iter: m.iter() diff --git a/source/rust_verify_test/tests/maps.rs b/source/rust_verify_test/tests/maps.rs index 152edeea59..dcd4b2d961 100644 --- a/source/rust_verify_test/tests/maps.rs +++ b/source/rust_verify_test/tests/maps.rs @@ -3,6 +3,19 @@ mod common; use common::*; +test_verify_one_file! { + #[test] test_map_from_set verus_code! { + use vstd::set::*; + use vstd::map::*; + + proof fn test_map_new() { + let s1 = Set::::empty().insert(1).insert(2).insert(3); + let m1 = Map::new(s1, |k: int| 10 * k); + assert(m1[2] == 20); + } + } => Ok(()) +} + test_verify_one_file! { #[test] test1 verus_code! { use vstd::set::*; @@ -10,10 +23,10 @@ test_verify_one_file! { proof fn test_map() { let s1 = Set::::empty().insert(1).insert(2).insert(3); - let m1 = s1.mk_map(|k: int| 10 * k); + let m1 = Map::new(s1, |k: int| 10 * k); assert(m1.index(2) == 20); let s2 = Set::::empty().insert(1).insert(3).insert(2); - let m2 = s2.mk_map(|k: int| 3 * k + 7 * k); + let m2 = Map::new(s2, |k: int| 3 * k + 7 * k); assert(m1 =~= m2); let m3 = map![10int => true ==> false, 20int => false ==> true]; assert(!m3.index(10)); @@ -25,8 +38,8 @@ test_verify_one_file! { proof fn testfun_eq() { let s = Set::::empty().insert(1).insert(2).insert(3); - let m1 = s.mk_map(|x: int| x + 4); - let m2 = s.mk_map(|y: int| y + (2 + 2)); + let m1 = Map::new(s, |x: int| x + 4); + let m2 = Map::new(s, |y: int| y + (2 + 2)); // m1 and m2 are equal even without extensional equality: assert(equal(m1, m2)); } @@ -40,11 +53,11 @@ test_verify_one_file! { proof fn test_map() { let s1 = Set::::empty().insert(1).insert(2).insert(3); - let m1 = s1.mk_map(|k: int| 10 * k); + let m1 = Map::new(s1, |k: int| 10 * k); assert(m1.index(2) == 20); assert(m1.index(4) == 40); // FAILS let s2 = Set::::empty().insert(1).insert(3).insert(2); - let m2 = s2.mk_map(|k: int| 3 * k + 7 * k); + let m2 = Map::new(s2, |k: int| 3 * k + 7 * k); assert(m1 =~= m2); } } => Err(err) => assert_one_fails(err) @@ -57,10 +70,10 @@ test_verify_one_file! { proof fn test_map() { let s1 = Set::::empty().insert(1).insert(2).insert(3); - let m1 = s1.mk_map(|k: int| 10 * k); + let m1 = Map::new(s1, |k: int| 10 * k); assert(m1.index(2) == 20); let s2 = Set::::empty().insert(1).insert(3).insert(2); - let m2 = s2.mk_map(|k: int| 3 * k + 8 * k); + let m2 = Map::new(s2, |k: int| 3 * k + 8 * k); assert(equal(m1, m2)) by {} // FAILS } } => Err(err) => assert_one_fails(err) @@ -73,7 +86,7 @@ test_verify_one_file! { proof fn test_map() { let s1 = Set::::empty().insert(1).insert(2).insert(3); - let m1 = s1.mk_map(|k: int| 10 * k); + let m1 = Map::new(s1, |k: int| 10 * k); let m3: Map = m1; let m4: Map = m1; // FAILS: see https://github.com/FStarLang/FStar/issues/1542 } @@ -88,8 +101,8 @@ test_verify_one_file! { #[verifier::auto_ext_equal(/* no auto_ext_equal */)] proof fn testfun_eq() { let s = Set::::empty().insert(1).insert(2).insert(3); - let m1 = s.mk_map(|x: int| x + 4); - let m2 = s.mk_map(|y: int| (2 + 2) + y); + let m1 = Map::new(s, |x: int| x + 4); + let m2 = Map::new(s, |y: int| (2 + 2) + y); // would require extensional equality: assert(m1 == m2); // FAILS } diff --git a/source/rust_verify_test/tests/open_invariant.rs b/source/rust_verify_test/tests/open_invariant.rs index 82dfed2c8c..5ce204d288 100644 --- a/source/rust_verify_test/tests/open_invariant.rs +++ b/source/rust_verify_test/tests/open_invariant.rs @@ -816,7 +816,7 @@ test_verify_one_file! { test_verify_one_file! { #[test] opens_invariants_set verus_code!{ use vstd::invariant::*; - use vstd::set::*; + use vstd::iset::*; struct P {} impl InvariantPredicate<(), ()> for P { @@ -827,7 +827,7 @@ test_verify_one_file! { tracked credit2: OpenInvariantCredit, tracked inv1: AtomicInvariant<(), (), P>, tracked inv2: AtomicInvariant<(), (), P>, - s: Set) + s: ISet) requires !s.contains(inv1.namespace()), !s.contains(inv2.namespace()), @@ -843,7 +843,7 @@ test_verify_one_file! { }); } - proof fn b(s: Set) + proof fn b(s: ISet) opens_invariants s { } @@ -853,7 +853,7 @@ test_verify_one_file! { test_verify_one_file! { #[test] opens_invariants_set_fails verus_code!{ use vstd::invariant::*; - use vstd::set::*; + use vstd::iset::*; struct P {} impl InvariantPredicate<(), ()> for P { @@ -862,7 +862,7 @@ test_verify_one_file! { proof fn a(tracked credit: OpenInvariantCredit, tracked inv: AtomicInvariant<(), (), P>, - s: Set) + s: ISet) opens_invariants any { @@ -871,7 +871,7 @@ test_verify_one_file! { }); } - proof fn b(s: Set) + proof fn b(s: ISet) opens_invariants s { } diff --git a/source/rust_verify_test/tests/raw_ptrs.rs b/source/rust_verify_test/tests/raw_ptrs.rs index 9c9aebb6ac..5a7dae5855 100644 --- a/source/rust_verify_test/tests/raw_ptrs.rs +++ b/source/rust_verify_test/tests/raw_ptrs.rs @@ -382,7 +382,7 @@ test_verify_one_file! { let b1_ptr = block_ptr as *mut u32; let b2_ptr = block_ptr.with_addr(block_ptr.addr() + 4) as *mut u32; - let tracked (token1, token2) = token.split(Set::new(|x: int| block_ptr.addr() <= x < block_ptr.addr() + 4)); + let tracked (token1, token2) = token.split(Set::range(block_ptr.addr() as int, (block_ptr.addr() + 4) as int)); let tracked mut token1 = token1.into_typed::(b1_ptr as usize); let tracked mut token2 = token2.into_typed::(b2_ptr as usize); diff --git a/source/rust_verify_test/tests/recursion.rs b/source/rust_verify_test/tests/recursion.rs index 1b7455896b..d3f6abf082 100644 --- a/source/rust_verify_test/tests/recursion.rs +++ b/source/rust_verify_test/tests/recursion.rs @@ -1544,7 +1544,7 @@ test_verify_one_file! { } test_verify_one_file! { - #[test] decrease_through_my_map verus_code! { + #[test] decrease_through_my_map_map verus_code! { // Err on the side of caution; see https://github.com/FStarLang/FStar/pull/2954 use vstd::prelude::*; @@ -1555,6 +1555,26 @@ test_verify_one_file! { x: MyMap>, } + spec fn f(s: S) -> int + decreases s + { + if s.x.0.dom().contains(3) { f(*s.x.0[3]) } else { 0 } + } + } => Ok(()) +} + +test_verify_one_file! { + #[test] decrease_through_my_map_imap verus_code! { + // Err on the side of caution; see https://github.com/FStarLang/FStar/pull/2954 + use vstd::prelude::*; + + #[verifier::reject_recursive_types(A)] + #[verifier::accept_recursive_types(B)] + struct MyMap(IMap); + struct S { + x: MyMap>, + } + spec fn f(s: S) -> int decreases s { diff --git a/source/rust_verify_test/tests/recursive_types.rs b/source/rust_verify_test/tests/recursive_types.rs index 866c0effa9..e477e64190 100644 --- a/source/rust_verify_test/tests/recursive_types.rs +++ b/source/rust_verify_test/tests/recursive_types.rs @@ -352,3 +352,61 @@ test_verify_one_file! { struct X { a: A, b: B, c: C, d: bool } } => Err(err) => assert_vir_error_msg(err, "duplicate parameter attribute A") } + +test_verify_one_file! { + #[test] test_recursive_set_map_ok verus_code! { + use vstd::std_specs::alloc::*; + use vstd::prelude::Set; + use vstd::prelude::Map; + + struct TreeNode { + value: int, + children: Set, + } + + enum RecursiveValue { + SingleValue { value: int }, + MultipleValues { values: Set }, + SingleMapping { mapping: Map }, + MultipleMappings { mappings: Set> }, + } + + } => Ok(()) +} + +test_verify_one_file! { + #[test] test_recursive_iset_bad verus_code! { + use vstd::std_specs::alloc::*; + use vstd::prelude::ISet; + + enum RecursiveValue { + SingleValue { value: int }, + MultipleValues { values: ISet }, + } + } => Err(err) => assert_vir_error_msg(err, "in a non-positive position") +} + +test_verify_one_file! { + #[test] test_recursive_imap_bad verus_code! { + use vstd::std_specs::alloc::*; + use vstd::prelude::IMap; + + enum RecursiveValue { + Value { value: int }, + Mapping { mapping: IMap }, + } + } => Err(err) => assert_vir_error_msg(err, "in a non-positive position") +} + +test_verify_one_file! { + #[test] test_recursive_set_imap_bad verus_code! { + use vstd::std_specs::alloc::*; + use vstd::prelude::Set; + use vstd::prelude::IMap; + + enum RecursiveValue { + Value { value: int }, + Mappings { mapping_set: Set> }, + } + } => Err(err) => assert_vir_error_msg(err, "in a non-positive position") +} diff --git a/source/rust_verify_test/tests/regression.rs b/source/rust_verify_test/tests/regression.rs index cf1e4e0ffe..cba5b2eccf 100644 --- a/source/rust_verify_test/tests/regression.rs +++ b/source/rust_verify_test/tests/regression.rs @@ -612,32 +612,32 @@ test_verify_one_file! { test_verify_one_file! { #[test] test_multiset_finite_false_1 verus_code! { use vstd::{map::*, multiset::*}; - proof fn test(mymap: Map) - requires !mymap.dom().finite() { + // This test was originally intended to ensure we couldn't use an + // infinite Map to construct a multiset, which is axiomatically finite. + // Now, Multiset::from_map requires a Finite Map as its argument, + // so finiteness is preserved by type construction. + // TODO(discuss): so shall we delete the test? + proof fn test(mymap: Map) { + assert(mymap.dom().finite()); let m = Multiset::from_map(mymap); assert(m.dom().finite()); - - assert(!m.dom().finite()); // FAILS - // assert(false); } - } => Err(err) => assert_one_fails(err) + } => Ok(()) } test_verify_one_file! { #[test] test_multiset_finite_false_2 verus_code! { use vstd::{map::*, multiset::*}; + // Obsoleted by same situation as _1 above. proof fn test(mymap: Map) - requires !mymap.dom().finite() { - + requires forall |k| mymap.contains_key(k) ==> mymap[k]>0, + { + assert(mymap.dom().finite()); let m = Multiset::from_map(mymap); - assert(m.dom().finite()); - - assert(m.dom() =~= mymap.dom()); // FAILS - // assert(!m.dom().finite()); - // assert(false); + assert(m.dom() =~= mymap.dom()); } - } => Err(err) => assert_one_fails(err) + } => Ok(()) } test_verify_one_file! { diff --git a/source/rust_verify_test/tests/seqs.rs b/source/rust_verify_test/tests/seqs.rs index 1913a98d2c..b38701d13c 100644 --- a/source/rust_verify_test/tests/seqs.rs +++ b/source/rust_verify_test/tests/seqs.rs @@ -33,15 +33,9 @@ test_verify_one_file! { assert(s8[0] == 5); assert(s8.len() == 10); - assert(s1.to_set().finite()) by { - seq_to_set_is_finite(s1); - } - assert(s6.to_set().finite()) by { - seq_to_set_is_finite(s6); - } - assert(s7.to_set().finite()) by { - seq_to_set_is_finite(s7); - } + assert(s1.to_iset().finite()); + assert(s6.to_iset().finite()); + assert(s7.to_iset().finite()); } } => Ok(()) } diff --git a/source/rust_verify_test/tests/sets.rs b/source/rust_verify_test/tests/sets.rs index 59a838c019..e5ed4ef276 100644 --- a/source/rust_verify_test/tests/sets.rs +++ b/source/rust_verify_test/tests/sets.rs @@ -25,11 +25,11 @@ test_verify_one_file! { test_verify_one_file! { #[test] test1 verus_code! { - use vstd::set::*; - use vstd::set_lib::*; + use vstd::iset::*; + use vstd::iset_lib::*; proof fn test_set() { - let nonneg = Set::new(|i: int| i >= 0); + let nonneg = ISet::new(|i: int| i >= 0); assert(forall|i: int| nonneg.contains(i) == (i >= 0)); let pos1 = nonneg.filter(|i: int| i > 0); assert(forall|i: int| pos1.contains(i) == (i > 0)); @@ -46,14 +46,14 @@ test_verify_one_file! { test_verify_one_file! { #[test] test1_fails1 verus_code! { - use vstd::set::*; + use vstd::iset::*; - pub closed spec fn set_map(s: Set, f: spec_fn(A) -> A) -> Set { - Set::new(|a: A| exists|x: A| s.contains(x) && a == f(x)) + pub closed spec fn set_map(s: ISet, f: spec_fn(A) -> A) -> ISet { + ISet::new(|a: A| exists|x: A| s.contains(x) && a == f(x)) } proof fn test_set() { - let nonneg = Set::new(|i: int| i >= 0); + let nonneg = ISet::new(|i: int| i >= 0); assert(forall|i: int| nonneg.contains(i) == (i >= 0)); let pos1 = nonneg.filter(|i: int| i > 0); assert(forall|i: int| pos1.contains(i) == (i > 0)); @@ -68,7 +68,7 @@ test_verify_one_file! { test_verify_one_file! { #[test] test_choose_assert_witness verus_code! { - use vstd::set::*; + use vstd::iset::*; #[verifier(opaque)] spec fn f(x: int) -> bool { @@ -78,7 +78,7 @@ test_verify_one_file! { proof fn test_witness() { assume(exists|x: int| f(x)); - let s = Set::new(|x: int| f(x)); + let s = ISet::new(|x: int| f(x)); assert(exists|x: int| f(x) && s.contains(x)); assert(s.contains(s.choose())); @@ -88,7 +88,7 @@ test_verify_one_file! { test_verify_one_file! { #[test] test_choose_fails_witness verus_code! { - use vstd::set::*; + use vstd::iset::*; #[verifier(opaque)] spec fn f(x: int) -> bool { @@ -98,25 +98,44 @@ test_verify_one_file! { proof fn test_witness() { assume(exists|x: int| f(x)); - let s = Set::new(|x: int| f(x)); + let s = ISet::new(|x: int| f(x)); assert(s.contains(s.choose())); // FAILS } } => Err(err) => assert_one_fails(err) } +test_verify_one_file! { + #[test] test_iset_fold verus_code! { + use vstd::iset::*; + + proof fn test() { + let s: ISet = iset![9]; + broadcast use {fold::lemma_fold_insert, fold::lemma_fold_empty}; + assert(s.finite()); + assert(s.len() > 0); + assert(s.fold(0, |p: nat, a: nat| p + a) == 9); + + assert(iset![].fold(0, |p: nat, a: nat| p + a) == 0); + } + } => Ok(()) +} + test_verify_one_file! { #[test] test_set_fold verus_code! { + use vstd::iset::*; use vstd::set::*; proof fn test() { let s: Set = set![9]; - broadcast use {fold::lemma_fold_insert, fold::lemma_fold_empty}; - assert(s.finite()); + assert(s.to_iset() =~= iset![9]); + broadcast use {vstd::iset::fold::lemma_fold_insert, vstd::iset::fold::lemma_fold_empty}; assert(s.len() > 0); assert(s.fold(0, |p: nat, a: nat| p + a) == 9); - assert(set![].fold(0, |p: nat, a: nat| p + a) == 0); + let s2: Set = set![]; + assert(s2.to_iset() =~= iset![]); + assert(s2.fold(0, |p: nat, a: nat| p + a) == 0); } } => Ok(()) } @@ -136,22 +155,22 @@ test_verify_one_file! { test_verify_one_file! { #[test] test_set_build verus_code! { + use vstd::iset::*; use vstd::set::*; proof fn test1() { let s = set_build!{ (x, x): (u8, u8) | x: u8 }; - assert(s.finite()); - let z = Set::new(|p: (u8, u8)| p.0 == p.1); - assert(s == z); + let z = ISet::new(|p: (u8, u8)| p.0 == p.1); + assert(s.congruent(z)); } proof fn test2() { let s = set_build!{ (x, x): (u8, u8) | exists x: u8 }; - let z = Set::new(|p: (u8, u8)| p.0 == p.1); + let z = ISet::new(|p: (u8, u8)| p.0 == p.1); // assert(s == z); // FAILS by itself, because of the "exists x: u8" - assert(s == z) by { + assert(s.congruent(z)) by { assert forall|p: (u8, u8)| p.0 == p.1 implies #[trigger] s.contains(p) by { // Exhibit the witness x of type u8 to trigger the "exists": assert(set_build!{ x: u8 }.contains(p.0)); @@ -161,9 +180,8 @@ test_verify_one_file! { proof fn test3() { let s = set_build!{ (x, y, x - y): (int, int, int) | x: int in 10..20, y: int in x..20, x + y != 25 }; - assert(s.finite()); - let z = Set::new(|t: (int, int, int)| 10 <= t.0 < 20 && t.0 <= t.1 < 20 && t.0 + t.1 != 25 && t.2 == t.0 - t.1); - assert(s == z); + let z = ISet::new(|t: (int, int, int)| 10 <= t.0 < 20 && t.0 <= t.1 < 20 && t.0 + t.1 != 25 && t.2 == t.0 - t.1); + assert(s.congruent(z)); } } => Ok(()) } diff --git a/source/rust_verify_test/tests/slices.rs b/source/rust_verify_test/tests/slices.rs index b90afa43d1..d833b78862 100644 --- a/source/rust_verify_test/tests/slices.rs +++ b/source/rust_verify_test/tests/slices.rs @@ -108,10 +108,10 @@ test_verify_one_file! { test_verify_one_file! { #[test] test_recursion_checks verus_code! { - use vstd::map::*; + use vstd::imap::*; struct Foo { - field: Box<[ Map ]>, + field: Box<[ IMap ]>, } } => Err(err) => assert_vir_error_msg(err, "non-positive position") diff --git a/source/rust_verify_test/tests/state_machines.rs b/source/rust_verify_test/tests/state_machines.rs index 8008a9b61c..7c35163394 100644 --- a/source/rust_verify_test/tests/state_machines.rs +++ b/source/rust_verify_test/tests/state_machines.rs @@ -7,6 +7,7 @@ const IMPORTS: &str = code_str! { #[allow(unused_imports)] use vstd::{atomic::*}; #[allow(unused_imports)] use vstd::{modes::*}; #[allow(unused_imports)] use vstd::prelude::*; + #[allow(unused_imports)] use vstd::imap::*; #[allow(unused_imports)] use vstd::map::*; #[allow(unused_imports)] use vstd::set::*; #[allow(unused_imports)] use vstd::multiset::*; @@ -30,7 +31,7 @@ test_verify_one_file! { #[test] dupe_name_fail IMPORTS.to_string() + verus_code_str! { state_machine!{ X { fields { - pub v: Map, + pub v: IMap, } transition!{ @@ -386,6 +387,23 @@ test_verify_one_file! { } => Err(e) => assert_vir_error_msg(e, "cannot be directly referenced here") } +test_verify_one_file! { + #[test] test_use_imap_directly IMPORTS.to_string() + verus_code_str! { + tokenized_state_machine!{ X { + fields { + #[sharding(imap)] pub t: IMap, + #[sharding(variable)] pub v: int, + } + + transition!{ + tr() { + update v = pre.t.index(0); + } + } + }} + } => Err(e) => assert_vir_error_msg(e, "cannot be directly referenced here") +} + test_verify_one_file! { #[test] test_use_multiset_directly IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { @@ -457,7 +475,7 @@ test_verify_one_file! { #[test] test_use_pre_no_field_withdraw_kv_value IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { fields { - #[sharding(storage_map)] pub v: Map, + #[sharding(storage_imap)] pub v: IMap, } transition!{ @@ -489,12 +507,12 @@ test_verify_one_file! { #[test] test_use_pre_no_field_withdraw_kv_key IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { fields { - #[sharding(storage_map)] pub v: Map, + #[sharding(storage_imap)] pub v: IMap, } init!{ initialize() { - init v = Map::empty(); + init v = IMap::empty(); } } @@ -509,8 +527,8 @@ test_verify_one_file! { verus!{ - proof fn foo(tracked m: Map) { - requires(equal(m, Map::empty())); + proof fn foo(tracked m: IMap) { + requires(equal(m, IMap::empty())); let tracked inst = X::Instance::initialize(m); let tracked t = (inst).tr(); @@ -1241,7 +1259,7 @@ test_verify_one_file! { tokenized_state_machine!{ X { fields { #[sharding(option)] - pub t: Map, + pub t: IMap, } }} } => Err(e) => assert_vir_error_msg(e, "must be of the form Option<_>") @@ -1280,6 +1298,17 @@ test_verify_one_file! { } => Err(e) => assert_vir_error_msg(e, "must be of the form Map<_, _>") } +test_verify_one_file! { + #[test] wrong_form_storage_imap IMPORTS.to_string() + verus_code_str! { + tokenized_state_machine!{ X { + fields { + #[sharding(storage_imap)] + pub t: int, + } + }} + } => Err(e) => assert_vir_error_msg(e, "must be of the form IMap<_, _>") +} + test_verify_one_file! { #[test] wrong_form_multiset IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { @@ -1302,6 +1331,17 @@ test_verify_one_file! { } => Err(e) => assert_vir_error_msg(e, "must be of the form Set<_>") } +test_verify_one_file! { + #[test] wrong_form_iset IMPORTS.to_string() + verus_code_str! { + tokenized_state_machine!{ X { + fields { + #[sharding(iset)] + pub t: Multiset, + } + }} + } => Err(e) => assert_vir_error_msg(e, "must be of the form ISet<_>") +} + test_verify_one_file! { #[test] wrong_form_count IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { @@ -2273,8 +2313,8 @@ test_verify_one_file! { #[test] inherent_safety_condition_map_withdraw IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { fields { - #[sharding(storage_map)] - pub t: Map + #[sharding(storage_imap)] + pub t: IMap } transition!{ @@ -2297,8 +2337,8 @@ test_verify_one_file! { #[test] inherent_safety_condition_map_withdraw_with_binding IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { fields { - #[sharding(storage_map)] - pub t: Map + #[sharding(storage_imap)] + pub t: IMap } transition!{ @@ -2365,8 +2405,8 @@ test_verify_one_file! { #[test] inherent_safety_condition_map_guard IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { fields { - #[sharding(storage_map)] - pub t: Map + #[sharding(storage_imap)] + pub t: IMap } property!{ @@ -2405,22 +2445,22 @@ test_verify_one_file! { #[test] inherent_safety_condition_map_general_guard IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { fields { - #[sharding(storage_map)] - pub t: Map + #[sharding(storage_imap)] + pub t: IMap } property!{ tr() { - guard t >= (Map::::empty().insert(5, 7)) by { }; // FAILS + guard t >= (IMap::::empty().insert(5, 7)) by { }; // FAILS birds_eye let t = pre.t; assert(t.dom().contains(5)) by { - assert(Map::::empty().insert(5, 7).dom().contains(5)); - assert(Map::::empty().insert(5, 7).index(5) == 7); + assert(IMap::::empty().insert(5, 7).dom().contains(5)); + assert(IMap::::empty().insert(5, 7).index(5) == 7); }; assert(t.index(5) == 7) by { - assert(Map::::empty().insert(5, 7).dom().contains(5)); - assert(Map::::empty().insert(5, 7).index(5) == 7); + assert(IMap::::empty().insert(5, 7).dom().contains(5)); + assert(IMap::::empty().insert(5, 7).index(5) == 7); }; } } @@ -2500,8 +2540,8 @@ test_verify_one_file! { #[test] inherent_safety_condition_map_deposit IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { fields { - #[sharding(storage_map)] - pub t: Map + #[sharding(storage_imap)] + pub t: IMap } transition!{ @@ -2827,7 +2867,7 @@ test_verify_one_file! { #[test] no_let_repeated_idents IMPORTS.to_string() + verus_code_str! { state_machine!{ X { fields { - pub t: Map + pub t: IMap } transition!{ @@ -2847,7 +2887,7 @@ test_verify_one_file! { #[test] no_let_repeated_idents2 IMPORTS.to_string() + verus_code_str! { state_machine!{ X { fields { - pub t: Map + pub t: IMap } transition!{ @@ -2864,7 +2904,7 @@ test_verify_one_file! { #[test] no_let_repeated_idents3 IMPORTS.to_string() + verus_code_str! { state_machine!{ X { fields { - pub t: Map + pub t: IMap } transition!{ @@ -2908,10 +2948,10 @@ test_verify_one_file! { #[test] type_recursion_fail_negative IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ X { fields { - // this should fail because Map has a reject_recursive_types first param + // this should fail because IMap has a reject_recursive_types first param #[sharding(variable)] - pub t: Map + pub t: IMap } }} } => Err(e) => assert_vir_error_msg(e, "non-positive position") @@ -3329,8 +3369,8 @@ test_verify_one_file! { #[sharding(storage_option)] pub storage_opt: Option, - #[sharding(storage_map)] - pub storage_map: Map, + #[sharding(storage_imap)] + pub storage_map: IMap, } transition!{ @@ -3572,8 +3612,8 @@ test_verify_one_file! { #[sharding(option)] pub opt: Option, - #[sharding(map)] - pub map: Map, + #[sharding(imap)] + pub map: IMap, #[sharding(multiset)] pub mset: Multiset, @@ -3581,8 +3621,8 @@ test_verify_one_file! { #[sharding(storage_option)] pub storage_opt: Option, - #[sharding(storage_map)] - pub storage_map: Map, + #[sharding(storage_imap)] + pub storage_map: IMap, } transition!{ @@ -3590,9 +3630,9 @@ test_verify_one_file! { remove opt -= ( Option::Some(5) ); add opt += ( Option::Some(8) ); - remove map -= ( Map::::empty().insert(0, 1) ); - have map >= ( Map::::empty().insert(2, 3) ); - add map += ( Map::::empty().insert(4, 5) ) by { assume(false); }; + remove map -= ( IMap::::empty().insert(0, 1) ); + have map >= ( IMap::::empty().insert(2, 3) ); + add map += ( IMap::::empty().insert(4, 5) ) by { assume(false); }; remove mset -= ( Multiset::::singleton(10) ); have mset >= ( Multiset::::singleton(11) ); @@ -3601,15 +3641,15 @@ test_verify_one_file! { withdraw storage_opt -= ( Option::Some(13) ) by { assume(false); }; deposit storage_opt += ( Option::Some(14) ); - withdraw storage_map -= ( Map::::empty().insert(15, 16) ) by { assume(false); }; - deposit storage_map += ( Map::::empty().insert(17, 18) ) by { assume(false); }; + withdraw storage_map -= ( IMap::::empty().insert(15, 16) ) by { assume(false); }; + deposit storage_map += ( IMap::::empty().insert(17, 18) ) by { assume(false); }; } } transition!{ tr2() { have opt >= (Option::Some(7)); - add map += (Map::::empty().insert(4, 5)) by { assume(false); }; + add map += (IMap::::empty().insert(4, 5)) by { assume(false); }; } } @@ -3633,9 +3673,9 @@ test_verify_one_file! { spec fn rel_tr1(pre: Y::State, post: Y::State) -> bool { &&& pre.opt == Option::Some(5) - &&& map![0 => 1].submap_of(pre.map) - &&& map![2 => 3].submap_of(pre.map.remove_keys(map![0 => 1int].dom())) - &&& pre.map.remove_keys(map![0 => 1int].dom()).dom().disjoint(map![4 => 5int].dom()) + &&& imap![0 => 1].submap_of(pre.map) + &&& imap![2 => 3].submap_of(pre.map.remove_keys(imap![0 => 1int].dom())) + &&& pre.map.remove_keys(imap![0 => 1int].dom()).dom().disjoint(imap![4 => 5int].dom()) ==> { @@ -3646,21 +3686,21 @@ test_verify_one_file! { ==> - (map![15 => 16].submap_of(pre.storage_map) + (imap![15 => 16].submap_of(pre.storage_map) ==> - (pre.storage_map.remove_keys(map![15 => 16int].dom()).dom().disjoint(map![17 => 18int].dom()) + (pre.storage_map.remove_keys(imap![15 => 16int].dom()).dom().disjoint(imap![17 => 18int].dom()) ==> { &&& post.opt == Option::Some(8) - &&& post.map == pre.map.remove_keys(map![0 => 1int].dom()).union_prefer_right(map![4 => 5]) + &&& post.map == pre.map.remove_keys(imap![0 => 1int].dom()).union_prefer_right(imap![4 => 5]) &&& post.mset == pre.mset.sub(Multiset::singleton(10)).add(Multiset::singleton(12)) &&& post.storage_opt == Option::Some(14) &&& post.storage_map == - pre.storage_map.remove_keys(map![15 => 16int].dom()).union_prefer_right(map![17 => 18]) + pre.storage_map.remove_keys(imap![15 => 16int].dom()).union_prefer_right(imap![17 => 18]) })))} } @@ -3668,10 +3708,10 @@ test_verify_one_file! { &&& pre.opt == Option::Some(5) &&& post.opt == Option::Some(8) - &&& map![0 => 1].submap_of(pre.map) - &&& map![2 => 3].submap_of(pre.map.remove_keys(map![0 => 1int].dom())) - &&& pre.map.remove_keys(map![0 => 1int].dom()).dom().disjoint(map![4 => 5int].dom()) - &&& post.map == pre.map.remove_keys(map![0 => 1int].dom()).union_prefer_right(map![4 => 5]) + &&& imap![0 => 1].submap_of(pre.map) + &&& imap![2 => 3].submap_of(pre.map.remove_keys(imap![0 => 1int].dom())) + &&& pre.map.remove_keys(imap![0 => 1int].dom()).dom().disjoint(imap![4 => 5int].dom()) + &&& post.map == pre.map.remove_keys(imap![0 => 1int].dom()).union_prefer_right(imap![4 => 5]) &&& Multiset::singleton(10).subset_of(pre.mset) &&& Multiset::singleton(11).subset_of(pre.mset.sub(Multiset::singleton(10))) @@ -3681,16 +3721,16 @@ test_verify_one_file! { &&& pre.storage_opt == Option::Some(13) &&& post.storage_opt == Option::Some(14) - &&& map![15 => 16].submap_of(pre.storage_map) - &&& pre.storage_map.remove_keys(map![15 => 16int].dom()).dom().disjoint(map![17 => 18int].dom()) + &&& imap![15 => 16].submap_of(pre.storage_map) + &&& pre.storage_map.remove_keys(imap![15 => 16int].dom()).dom().disjoint(imap![17 => 18int].dom()) &&& post.storage_map == - pre.storage_map.remove_keys(map![15 => 16int].dom()).union_prefer_right(map![17 => 18]) + pre.storage_map.remove_keys(imap![15 => 16int].dom()).union_prefer_right(imap![17 => 18]) } spec fn rel_tr2(pre: Y::State, post: Y::State) -> bool { &&& pre.opt == Option::Some(7) &&& !pre.map.dom().contains(4) ==> { - &&& post.map == pre.map.union_prefer_right(map![4 => 5]) + &&& post.map == pre.map.union_prefer_right(imap![4 => 5]) &&& post.opt == pre.opt &&& post.storage_opt == pre.storage_opt &&& post.storage_map == pre.storage_map @@ -3701,7 +3741,7 @@ test_verify_one_file! { spec fn rel_tr2_strong(pre: Y::State, post: Y::State) -> bool { &&& pre.opt == Option::Some(7) &&& !pre.map.dom().contains(4) - &&& post.map == pre.map.union_prefer_right(map![4 => 5]) + &&& post.map == pre.map.union_prefer_right(imap![4 => 5]) &&& post.opt == pre.opt &&& post.storage_opt == pre.storage_opt &&& post.storage_map == pre.storage_map @@ -4267,18 +4307,18 @@ test_verify_one_file! { #[sharding(option)] pub opt: Option, - #[sharding(map)] - pub map: Map, + #[sharding(imap)] + pub map: IMap, - #[sharding(storage_map)] - pub storage_map: Map, + #[sharding(storage_imap)] + pub storage_map: IMap, } init!{ initialize() { init opt = Option::Some(2); - init map = Map::::empty().insert(1, 5); - init storage_map = Map::::empty().insert(1, 6); + init map = IMap::::empty().insert(1, 5); + init storage_map = IMap::::empty().insert(1, 6); } } @@ -4469,7 +4509,7 @@ test_verify_one_file! { } proof fn do_tokens() { - let tracked mut m: Map = Map::tracked_empty(); + let tracked mut m: IMap = IMap::tracked_empty(); m.tracked_insert(1, 6u64); let tracked (Tracked(inst), Tracked(opt_token), Tracked(mut map_tokens)) = Y::Instance::initialize(m); @@ -4870,8 +4910,8 @@ test_verify_one_file! { #[test] persistent_map_remove_fail IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ Y { fields { - #[sharding(persistent_map)] - pub c: Map, + #[sharding(persistent_imap)] + pub c: IMap, } transition!{ @@ -4955,8 +4995,8 @@ test_verify_one_file! { #[test] persistent_set_remove_fail IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ Y { fields { - #[sharding(persistent_set)] - pub c: Set, + #[sharding(persistent_iset)] + pub c: ISet, } transition!{ @@ -5142,13 +5182,13 @@ test_verify_one_file! { #[test] persistent_map_codegen IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ Y { fields { - #[sharding(persistent_map)] - pub c: Map, + #[sharding(persistent_imap)] + pub c: IMap, } init!{ initialize() { - init c = Map::empty().insert(1, 2); + init c = IMap::empty().insert(1, 2); } } @@ -5162,7 +5202,7 @@ test_verify_one_file! { transition!{ tr2() { add c (union)= ( - Map::empty().insert(5, 9).insert(12, 15) + IMap::empty().insert(5, 9).insert(12, 15) ); } } @@ -5170,7 +5210,7 @@ test_verify_one_file! { transition!{ tr3() { have c >= ( - Map::empty().insert(5, 9).insert(12, 15) + IMap::empty().insert(5, 9).insert(12, 15) ); } } @@ -5255,25 +5295,25 @@ test_verify_one_file! { rel_tr3(pre, post) == Y::State::tr3(pre, post), rel_tr3_strong(pre, post) == Y::State::tr3_strong(pre, post), { - assert_maps_equal!( + assert_imaps_equal!( pre.c.insert(5, 9).insert(12, 15), pre.c.union_prefer_right( - Map::empty().insert(5, 9).insert(12, 15) + IMap::empty().insert(5, 9).insert(12, 15) ) ); if rel_tr3(pre, post) { assert( - Map::empty().insert(5, 9).insert(12, 15).submap_of(pre.c) + IMap::empty().insert(5, 9).insert(12, 15).submap_of(pre.c) ); assert(Y::State::tr3(pre, post)); } if Y::State::tr3(pre, post) { assert( - Map::::empty().insert(5, 9).insert(12, 15).dom().contains(5) + IMap::::empty().insert(5, 9).insert(12, 15).dom().contains(5) ); assert( - Map::::empty().insert(5, 9).insert(12, 15).dom().contains(12) + IMap::::empty().insert(5, 9).insert(12, 15).dom().contains(12) ); assert(pre.c.dom().contains(5)); assert(pre.c.dom().contains(12)); @@ -5296,7 +5336,7 @@ test_verify_one_file! { assert(m_3.value() == 4); let tracked m_5_12 = inst.tr2(); - assert(m_5_12.map() =~= map![5 => 9, 12 => 15]); + assert(m_5_12.map() =~= imap![5 => 9, 12 => 15]); assert(m_5_12.instance_id() == inst.id()); inst.tr3(&m_5_12); @@ -5316,8 +5356,8 @@ test_verify_one_file! { tokenized_state_machine!{ Y { fields { - #[sharding(storage_map)] - pub storage_m: Map, + #[sharding(storage_imap)] + pub storage_m: IMap, #[sharding(storage_option)] pub storage_opt: Option, @@ -5384,11 +5424,11 @@ test_verify_one_file! { tokenized_state_machine!{ Y { fields { - #[sharding(map)] - pub m: Map, + #[sharding(imap)] + pub m: IMap, - #[sharding(storage_map)] - pub storage_m: Map, + #[sharding(storage_imap)] + pub storage_m: IMap, #[sharding(option)] pub opt: Option, @@ -5398,7 +5438,7 @@ test_verify_one_file! { } init!{ - initialize(m: Map, opt: Option) { + initialize(m: IMap, opt: Option) { init m = m; init storage_m = m; init opt = opt; @@ -5407,7 +5447,7 @@ test_verify_one_file! { } #[inductive(initialize)] - fn initialize_inductive(post: Self, m: Map, opt: Option) { } + fn initialize_inductive(post: Self, m: IMap, opt: Option) { } transition!{ tr1() { @@ -5819,11 +5859,11 @@ test_verify_one_file! { } proof fn test_inst1() { - let tracked mut p_m = Map::tracked_empty(); + let tracked mut p_m = IMap::tracked_empty(); p_m.tracked_insert(1, Goo::Bar); let tracked (Tracked(inst), Tracked(mut m_token), Tracked(opt_token)) = Y::Instance::initialize( - map![1 => Goo::Bar], + imap![1 => Goo::Bar], Option::Some(Goo::Bar), p_m, Option::Some(Goo::Bar), @@ -5846,11 +5886,11 @@ test_verify_one_file! { } proof fn test_inst2() { - let tracked mut p_m = Map::tracked_empty(); + let tracked mut p_m = IMap::tracked_empty(); p_m.tracked_insert(1, Goo::Qux(8u64)); let tracked (Tracked(inst), Tracked(mut m_token), Tracked(opt_token)) = Y::Instance::initialize( - map![1 => Goo::Qux(8u64)], + imap![1 => Goo::Qux(8u64)], Option::Some(Goo::Qux(8u64)), p_m, Option::Some(Goo::Qux(8u64)), @@ -5873,11 +5913,11 @@ test_verify_one_file! { } proof fn test_inst3() { - let tracked mut p_m = Map::tracked_empty(); + let tracked mut p_m = IMap::tracked_empty(); p_m.tracked_insert(1, Goo::Tal(8u64, 9u64)); let tracked (Tracked(inst), Tracked(mut m_token), Tracked(opt_token)) = Y::Instance::initialize( - map![1 => Goo::Tal(8u64, 9u64)], + imap![1 => Goo::Tal(8u64, 9u64)], Option::Some(Goo::Tal(8u64, 9u64)), p_m, Option::Some(Goo::Tal(8u64, 9u64)), @@ -6590,13 +6630,13 @@ test_verify_one_file! { #[test] persistent_set_codegen IMPORTS.to_string() + verus_code_str! { tokenized_state_machine!{ Y { fields { - #[sharding(persistent_set)] - pub b: Set, + #[sharding(persistent_iset)] + pub b: ISet, } init!{ initialize() { - init b = Set::::empty().insert(19); + init b = ISet::::empty().insert(19); } } @@ -6614,13 +6654,13 @@ test_verify_one_file! { transition!{ tr_add_gen() { - add b (union)= (Set::::empty().insert(6)); + add b (union)= (ISet::::empty().insert(6)); } } transition!{ tr_have_gen() { - have b >= (Set::::empty().insert(6)); + have b >= (ISet::::empty().insert(6)); } } }} @@ -6646,11 +6686,11 @@ test_verify_one_file! { } spec fn rel_tr4(pre: Y::State, post: Y::State) -> bool { - post.b == pre.b.union(Set::::empty().insert(6)) + post.b == pre.b.union(ISet::::empty().insert(6)) } spec fn rel_tr4_strong(pre: Y::State, post: Y::State) -> bool { - post.b == pre.b.union(Set::::empty().insert(6)) + post.b == pre.b.union(ISet::::empty().insert(6)) } spec fn rel_tr5(pre: Y::State, post: Y::State) -> bool { @@ -6679,8 +6719,8 @@ test_verify_one_file! { proof fn test_inst1() { let tracked (Tracked(inst), Tracked(token_f)) = Y::Instance::initialize(); - assert(Set::::empty().insert(19).contains(19)); - assert(token_f.set() =~= set![19]); + assert(ISet::::empty().insert(19).contains(19)); + assert(token_f.set() =~= iset![19]); assert(token_f.instance_id() == inst.id()); let tracked token1 = inst.tr_add(); @@ -6692,8 +6732,8 @@ test_verify_one_file! { assert(equal(token1_clone, token1)); let tracked token_set = inst.tr_add_gen(); - assert(Set::::empty().insert(6).contains(6)); - assert(token_set.set() =~= set![6]); + assert(ISet::::empty().insert(6).contains(6)); + assert(token_set.set() =~= iset![6]); assert(token_set.instance_id() == inst.id()); inst.tr_have_gen(&token_set); } @@ -6776,7 +6816,7 @@ test_verify_one_file! { state_machine!{ X { fields { - pub m: Map, + pub m: IMap, pub a: int, pub b: int, @@ -6812,7 +6852,7 @@ test_verify_one_file! { state_machine!{ X { fields { - pub m: Map, + pub m: IMap, pub a: int, pub b: int, @@ -6909,7 +6949,7 @@ test_verify_one_file! { state_machine!{ X { fields { - pub m: Map, + pub m: IMap, pub a: int, pub b: int, @@ -7035,7 +7075,7 @@ test_verify_one_file! { state_machine!{ X { fields { - pub m: Map, + pub m: IMap, } transition!{ @@ -7287,12 +7327,12 @@ test_verify_one_file! { tokenized_state_machine!{ A { fields { #[sharding(variable)] - pub x: Map, + pub x: IMap, } init!{ initialize() { - init x = Map::::empty(); + init x = IMap::::empty(); } } }} @@ -7309,13 +7349,13 @@ test_verify_one_file! { tokenized_state_machine!{ A { fields { - #[sharding(persistent_map)] - pub x: Map, + #[sharding(persistent_imap)] + pub x: IMap, } init!{ initialize() { - init x = Map::::empty(); + init x = IMap::::empty(); } } }} @@ -7357,12 +7397,12 @@ test_verify_one_file! { tokenized_state_machine!{ X { fields { - #[sharding(map)] - pub m: Map, + #[sharding(imap)] + pub m: IMap, } init!{ - initialize(m: Map) { + initialize(m: IMap) { init m = m; } } @@ -7378,16 +7418,16 @@ test_verify_one_file! { } proof fn test() { - let tracked (Tracked(inst), Tracked(mut map_token)) = X::Instance::::initialize(map![3 => B { i: 9 }]); + let tracked (Tracked(inst), Tracked(mut map_token)) = X::Instance::::initialize(imap![3 => B { i: 9 }]); assert(map_token.instance_id() == inst.id()); - assert(map_token.map() =~= map![3 => B { i: 9 }]); + assert(map_token.map() =~= imap![3 => B { i: 9 }]); let tracked r = map_token.remove(3); assert(r.key() == 3); assert(r.value() == B { i: 9 }); assert(r.instance_id() == inst.id()); let j = X::m_map::::empty(inst.id()); - assert(j.map() =~= map![]); + assert(j.map() =~= imap![]); } } => Ok(()) } diff --git a/source/rust_verify_test/tests/summer_school.rs b/source/rust_verify_test/tests/summer_school.rs index cd815f2992..64486656cf 100644 --- a/source/rust_verify_test/tests/summer_school.rs +++ b/source/rust_verify_test/tests/summer_school.rs @@ -562,6 +562,8 @@ fn e13_pass() { test_verify_one_file! { #[test] e14_pass verus_code! { + use vstd::iset::*; + use vstd::iset_lib::*; use vstd::set::*; use vstd::set_lib::*; use vstd::map::*; @@ -572,11 +574,11 @@ test_verify_one_file! { } proof fn set_comprehension() { - let modest_evens = Set::new(|x: int| 0 <= x < 10 && is_even(x)); - assert(modest_evens =~= set![0, 2, 4, 6, 8]); + let modest_evens = ISet::new(|x: int| 0 <= x < 10 && is_even(x)); + assert(modest_evens =~= iset![0, 2, 4, 6, 8]); /* This is beyond summer school, but shows a verus-preferred style */ - let equivalent_evens = set_int_range(0, 10).filter(|x: int| is_even(x)); + let equivalent_evens = Set::range(0, 10int).filter(|x: int| is_even(x)).to_iset(); assert(modest_evens =~= equivalent_evens); } @@ -591,12 +593,12 @@ test_verify_one_file! { assert(replace_map[3] == 7); /* This is beyond summer school, but shows a verus-preferred style */ - let equivalent_double_map = set_int_range(1, 5).mk_map(|x: int| x * 2); + let equivalent_double_map = Map::new(Set::range(1, 5int), |x: int| x * 2); assert(equivalent_double_map =~= double_map); } proof fn map_comprehension() { - let doubly_map = set_int_range(0, 5).mk_map(|x: int| 2 * x); + let doubly_map = Map::new(Set::range(0, 5int), |x: int| 2 * x); assert(doubly_map[1] == 2); assert(doubly_map[4] == 8); } @@ -611,6 +613,8 @@ test_verify_one_file! { test_verify_one_file! { #[test] e14_fail verus_code! { + use vstd::iset::*; + use vstd::iset_lib::*; use vstd::set::*; use vstd::set_lib::*; use vstd::seq::*; @@ -621,8 +625,8 @@ test_verify_one_file! { } proof fn set_comprehension() { - let modest_evens = Set::new(|x: int| 0 <= x < 10 && is_even(x)); - assert(modest_evens =~= set![0, 2, 4, 8]); // FAILS + let modest_evens = ISet::new(|x: int| 0 <= x < 10 && is_even(x)); + assert(modest_evens =~= iset![0, 2, 4, 8]); // FAILS } proof fn maps() { @@ -637,7 +641,7 @@ test_verify_one_file! { } proof fn map_comprehension() { - let doubly_map = set_int_range(0, 5).mk_map(|x: int| 2 * x); + let doubly_map = Map::new(Set::range(0, 5int), |x: int| 2 * x); assert(doubly_map[1] == 2); assert(doubly_map[4] == 4); // FAILS } @@ -652,6 +656,8 @@ test_verify_one_file! { test_verify_one_file! { #[test] e15_pass verus_code! { + use vstd::iset::*; + use vstd::iset_lib::*; use vstd::set::*; use vstd::set_lib::*; @@ -664,16 +670,14 @@ test_verify_one_file! { } proof fn is_this_set_finite() { - let modest_evens = Set::new(|x: int| is_modest(x) && is_even(x)); + let modest_evens = ISet::new(|x: int| is_modest(x) && is_even(x)); // In verus, unlike Dafny, it's fine to have infinite sets, but you may want a finite // one (say because you're using it as a decreases to well-found an induction). - let modest_numbers = set_int_range(0, 10); - // TODO(chris): we need ambient automation for lemmes. lemma_int_range shoud be in a - // low-risk kit. - lemma_int_range(0, 10); + let modest_numbers = Set::range(0, 10int).to_iset(); + assert( modest_numbers.finite() ); // TODO(chris): don't want to have type annotation on this lemma, but there's an // erasure bug. - lemma_len_subset::(modest_evens, modest_numbers); + vstd::iset_lib::lemma_len_subset::(modest_evens, modest_numbers); assert(modest_evens.finite()); } } => Ok(()) @@ -681,7 +685,7 @@ test_verify_one_file! { test_verify_one_file! { #[test] e15_fail verus_code! { - use vstd::set::*; + use vstd::iset::*; spec fn is_modest(x: int) -> bool { 0 <= x < 10 @@ -692,7 +696,7 @@ test_verify_one_file! { } proof fn is_this_set_finite() { - let modest_evens = Set::new(|x: int| is_modest(x) && is_even(x)); + let modest_evens = ISet::new(|x: int| is_modest(x) && is_even(x)); // Need additional proof to show that this construction is finite. assert(modest_evens.finite()); // FAILS } diff --git a/source/rust_verify_test/tests/triggers.rs b/source/rust_verify_test/tests/triggers.rs index 474c6d93c6..5573c103d3 100644 --- a/source/rust_verify_test/tests/triggers.rs +++ b/source/rust_verify_test/tests/triggers.rs @@ -615,13 +615,14 @@ test_verify_one_file! { test_verify_one_file! { #[test] issue2123 verus_code! { use vstd::prelude::*; + use vstd::iset::*; pub enum A { Foo { xyz: Result<(), ()> }, } - pub open spec fn foo(a: A) -> Set { - Set::new(|x: nat| { + pub open spec fn foo(a: A) -> ISet { + ISet::new(|x: nat| { match a { A::Foo { xyz, .. } => { let _ = xyz.ok(); diff --git a/source/state_machines_macros/src/ast.rs b/source/state_machines_macros/src/ast.rs index 8e11d8146b..e4ddbdd7e8 100644 --- a/source/state_machines_macros/src/ast.rs +++ b/source/state_machines_macros/src/ast.rs @@ -65,19 +65,24 @@ pub enum ShardableType { Option(Type), Map(Type, Type), + IMap(Type, Type), Multiset(Type), Set(Type), + ISet(Type), Count, Bool, PersistentMap(Type, Type), + PersistentIMap(Type, Type), PersistentOption(Type), PersistentSet(Type), + PersistentISet(Type), PersistentCount, PersistentBool, StorageOption(Type), StorageMap(Type, Type), + StorageIMap(Type, Type), } #[derive(Clone, Debug, PartialEq, Eq, Copy)] @@ -447,19 +452,24 @@ impl ShardableType { ShardableType::Option(_) => "option", ShardableType::Map(_, _) => "map", + ShardableType::IMap(_, _) => "imap", ShardableType::Multiset(_) => "multiset", ShardableType::Set(_) => "set", + ShardableType::ISet(_) => "iset", ShardableType::Count => "count", ShardableType::Bool => "bool", ShardableType::PersistentMap(_, _) => "persistent_map", + ShardableType::PersistentIMap(_, _) => "persistent_imap", ShardableType::PersistentOption(_) => "persistent_option", ShardableType::PersistentSet(_) => "persistent_set", + ShardableType::PersistentISet(_) => "persistent_iset", ShardableType::PersistentCount => "persistent_count", ShardableType::PersistentBool => "persistent_bool", ShardableType::StorageOption(_) => "storage_option", ShardableType::StorageMap(_, _) => "storage_map", + ShardableType::StorageIMap(_, _) => "storage_imap", } } @@ -473,7 +483,9 @@ impl ShardableType { pub fn is_storage(&self) -> bool { match self { - ShardableType::StorageOption(_) | ShardableType::StorageMap(_, _) => true, + ShardableType::StorageOption(_) + | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) => true, ShardableType::Variable(_) | ShardableType::Constant(_) @@ -481,22 +493,28 @@ impl ShardableType { | ShardableType::Multiset(_) | ShardableType::Option(_) | ShardableType::Map(_, _) + | ShardableType::IMap(_, _) | ShardableType::PersistentMap(_, _) + | ShardableType::PersistentIMap(_, _) | ShardableType::PersistentOption(_) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::PersistentCount | ShardableType::PersistentBool | ShardableType::Count | ShardableType::Bool - | ShardableType::Set(_) => false, + | ShardableType::Set(_) + | ShardableType::ISet(_) => false, } } pub fn is_persistent(&self) -> bool { match self { ShardableType::PersistentMap(_, _) + | ShardableType::PersistentIMap(_, _) | ShardableType::PersistentOption(_) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::PersistentCount | ShardableType::PersistentBool => true, @@ -506,9 +524,12 @@ impl ShardableType { | ShardableType::Multiset(_) | ShardableType::Option(_) | ShardableType::Set(_) + | ShardableType::ISet(_) | ShardableType::Map(_, _) + | ShardableType::IMap(_, _) | ShardableType::StorageOption(_) | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) | ShardableType::Bool | ShardableType::Count => false, } @@ -535,7 +556,9 @@ impl ShardableType { pub fn get_map_key_type(&self) -> Type { match self { ShardableType::Map(key, _val) => key.clone(), + ShardableType::IMap(key, _val) => key.clone(), ShardableType::StorageMap(key, _val) => key.clone(), + ShardableType::StorageIMap(key, _val) => key.clone(), _ => panic!("get_map_key_type expected map"), } } @@ -544,7 +567,9 @@ impl ShardableType { pub fn get_map_value_type(&self) -> Type { match self { ShardableType::Map(_key, val) => val.clone(), + ShardableType::IMap(_key, val) => val.clone(), ShardableType::StorageMap(_key, val) => val.clone(), + ShardableType::StorageIMap(_key, val) => val.clone(), _ => panic!("get_map_value_type expected map"), } } diff --git a/source/state_machines_macros/src/concurrency_tokens.rs b/source/state_machines_macros/src/concurrency_tokens.rs index 01b5490590..46af8465e3 100644 --- a/source/state_machines_macros/src/concurrency_tokens.rs +++ b/source/state_machines_macros/src/concurrency_tokens.rs @@ -57,19 +57,23 @@ fn nondeterministic_read_spec_out_name(field: &Field) -> Ident { fn stored_object_type(field: &Field) -> Type { match &field.stype { ShardableType::StorageOption(ty) => ty.clone(), - ShardableType::StorageMap(_key, ty) => ty.clone(), + ShardableType::StorageMap(_key, ty) | ShardableType::StorageIMap(_key, ty) => ty.clone(), ShardableType::Variable(_) | ShardableType::Constant(_) | ShardableType::NotTokenized(_) | ShardableType::Option(_) | ShardableType::Map(_, _) + | ShardableType::IMap(_, _) | ShardableType::PersistentOption(_) | ShardableType::PersistentMap(_, _) + | ShardableType::PersistentIMap(_, _) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::PersistentCount | ShardableType::PersistentBool | ShardableType::Multiset(_) | ShardableType::Set(_) + | ShardableType::ISet(_) | ShardableType::Bool | ShardableType::Count => { panic!("stored_object_type"); @@ -250,6 +254,16 @@ fn token_struct_stream(sm: &SM, field: &Field) -> TokenStream { pub type #name#gen1 #genwhere = #vstd::tokens::MapToken<#key, #val, #tok>; } } + ShardableType::IMap(key, val) | ShardableType::PersistentIMap(key, val) => { + let name = Ident::new(&format!("{:}_map", field.name), field.name.span()); + let (gen1, genwhere) = generics_for_decl(&sm.generics); + let tok = field_token_type(sm, field); + quote_vstd! { vstd => + #[allow(type_alias_bounds)] + #[allow(non_camel_case_types)] + pub type #name#gen1 #genwhere = #vstd::tokens::IMapToken<#key, #val, #tok>; + } + } ShardableType::Set(elem) | ShardableType::PersistentSet(elem) => { let name = Ident::new(&format!("{}_set", field.name), field.name.span()); let (gen1, genwhere) = generics_for_decl(&sm.generics); @@ -260,6 +274,16 @@ fn token_struct_stream(sm: &SM, field: &Field) -> TokenStream { pub type #name#gen1 #genwhere = #vstd::tokens::SetToken<#elem, #tok>; } } + ShardableType::ISet(elem) | ShardableType::PersistentISet(elem) => { + let name = Ident::new(&format!("{:}_set", field.name), field.name.span()); + let (gen1, genwhere) = generics_for_decl(&sm.generics); + let tok = field_token_type(sm, field); + quote_vstd! { vstd => + #[allow(type_alias_bounds)] + #[allow(non_camel_case_types)] + pub type #name#gen1 #genwhere = #vstd::tokens::ISetToken<#elem, #tok>; + } + } ShardableType::Multiset(elem) => { let name = Ident::new(&format!("{}_multiset", field.name), field.name.span()); let (gen1, genwhere) = generics_for_decl(&sm.generics); @@ -345,7 +369,9 @@ pub fn output_token_types_and_fns( ShardableType::NotTokenized(_) => { // don't need to add a struct in this case } - ShardableType::StorageOption(_) | ShardableType::StorageMap(_, _) => { + ShardableType::StorageOption(_) + | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) => { // storage types don't have tokens; the 'token type' is just the // the type of the field } @@ -353,10 +379,14 @@ pub fn output_token_types_and_fns( | ShardableType::Option(_) | ShardableType::PersistentOption(_) | ShardableType::Map(..) + | ShardableType::IMap(..) | ShardableType::PersistentMap(..) + | ShardableType::PersistentIMap(..) | ShardableType::Multiset(_) | ShardableType::Set(_) + | ShardableType::ISet(_) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::Count | ShardableType::PersistentCount | ShardableType::Bool @@ -512,16 +542,21 @@ pub fn exchange_stream( ShardableType::Multiset(_) | ShardableType::Option(_) | ShardableType::Map(_, _) + | ShardableType::IMap(_, _) | ShardableType::PersistentOption(_) | ShardableType::PersistentMap(_, _) + | ShardableType::PersistentIMap(_, _) | ShardableType::Count | ShardableType::PersistentCount | ShardableType::Bool | ShardableType::PersistentBool | ShardableType::Set(_) + | ShardableType::ISet(_) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::StorageOption(_) - | ShardableType::StorageMap(_, _) => { + | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) => { params.insert(field.name.to_string(), Vec::new()); } ShardableType::Variable(_) @@ -856,17 +891,22 @@ pub fn exchange_stream( } ShardableType::Option(_) | ShardableType::Map(_, _) + | ShardableType::IMap(_, _) | ShardableType::Set(_) + | ShardableType::ISet(_) | ShardableType::Multiset(_) | ShardableType::Count | ShardableType::Bool | ShardableType::PersistentOption(_) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::PersistentMap(_, _) + | ShardableType::PersistentIMap(_, _) | ShardableType::PersistentCount | ShardableType::PersistentBool | ShardableType::StorageOption(_) - | ShardableType::StorageMap(_, _) => { + | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) => { // These sharding types all use the SpecialOps. The earlier translation // phase has already processed those and established all the necessary // pre-conditions and post-conditions, and it has also established @@ -1035,12 +1075,16 @@ fn get_init_param_input_type(_sm: &SM, field: &Field) -> Option { ShardableType::NotTokenized(_) => None, ShardableType::Multiset(_) => None, ShardableType::Set(_) => None, + ShardableType::ISet(_) => None, ShardableType::Bool => None, ShardableType::Option(_) => None, ShardableType::Map(_, _) => None, + ShardableType::IMap(_, _) => None, ShardableType::PersistentOption(_) => None, ShardableType::PersistentSet(_) => None, + ShardableType::PersistentISet(_) => None, ShardableType::PersistentMap(_, _) => None, + ShardableType::PersistentIMap(_, _) => None, ShardableType::PersistentCount => None, ShardableType::PersistentBool => None, ShardableType::Count => None, @@ -1050,6 +1094,9 @@ fn get_init_param_input_type(_sm: &SM, field: &Field) -> Option { ShardableType::StorageMap(key, val) => Some(Type::Verbatim(quote_vstd! { vstd => #vstd::map::Map<#key, #val> })), + ShardableType::StorageIMap(key, val) => Some(Type::Verbatim(quote_vstd! { vstd => + #vstd::imap::IMap<#key, #val> + })), } } @@ -1060,7 +1107,9 @@ fn add_initialization_input_conditions( param_value: Expr, ) { match &field.stype { - ShardableType::StorageOption(_) | ShardableType::StorageMap(_, _) => { + ShardableType::StorageOption(_) + | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) => { requires.push(mk_eq(param_value.span(), ¶m_value, &init_value)); } _ => { @@ -1081,12 +1130,17 @@ fn get_init_param_output_type(sm: &SM, field: &Field) -> Option { | ShardableType::Bool | ShardableType::PersistentBool | ShardableType::Set(_) + | ShardableType::ISet(_) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::Map(..) - | ShardableType::PersistentMap(..) => Some(field_token_collection_type(sm, field)), + | ShardableType::IMap(..) + | ShardableType::PersistentMap(..) + | ShardableType::PersistentIMap(..) => Some(field_token_collection_type(sm, field)), ShardableType::StorageOption(_) => None, // no output tokens for storage ShardableType::StorageMap(_, _) => None, + ShardableType::StorageIMap(_, _) => None, } } @@ -1121,9 +1175,13 @@ fn add_initialization_output_conditions( | ShardableType::Bool | ShardableType::PersistentBool | ShardableType::Set(_) + | ShardableType::ISet(_) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::Map(_, _) + | ShardableType::IMap(_, _) | ShardableType::PersistentMap(_, _) + | ShardableType::PersistentIMap(_, _) | ShardableType::Multiset(_) => { ensures.push(relation_for_collection_of_internal_tokens( sm, @@ -1186,6 +1244,17 @@ fn relation_for_collection_of_internal_tokens( && #vstd::prelude::equal((#param_value).instance_id(), #inst_value) }) } + ShardableType::ISet(_) | ShardableType::PersistentISet(_) => { + let fncall = if strict { + quote_spanned_vstd! { vstd, span => #vstd::prelude::equal } + } else { + quote_spanned_vstd! { vstd, span => #vstd::iset::ISet::spec_le } + }; + Expr::Verbatim(quote_spanned_vstd! { vstd, span => + #fncall(#given_value, (#param_value).set()) + && #vstd::prelude::equal((#param_value).instance_id(), #inst_value) + }) + } ShardableType::Map(_, _) | ShardableType::PersistentMap(_, _) => { let fncall = if strict { quote_spanned_vstd! { vstd, span => #vstd::prelude::equal } @@ -1197,6 +1266,17 @@ fn relation_for_collection_of_internal_tokens( && #vstd::prelude::equal((#param_value).instance_id(), #inst_value) }) } + ShardableType::IMap(_, _) | ShardableType::PersistentIMap(_, _) => { + let fncall = if strict { + quote_spanned_vstd! { vstd, span => #vstd::prelude::equal } + } else { + quote_spanned_vstd! { vstd, span => #vstd::imap::IMap::spec_le } + }; + Expr::Verbatim(quote_spanned_vstd! { vstd, span => + #fncall(#given_value, (#param_value).map()) + && #vstd::prelude::equal((#param_value).instance_id(), #inst_value) + }) + } ShardableType::Multiset(_) => { let fncall = if strict { quote_spanned_vstd! { vstd, span => #vstd::prelude::equal } @@ -1238,6 +1318,15 @@ fn traits_stream(sm: &SM, field: &Field) -> TokenStream { matches!(&field.stype, ShardableType::Set(_)), ) } + ShardableType::ISet(ty) | ShardableType::PersistentISet(ty) => { + let token_ty = field_token_type(sm, field); + token_trait_impls( + &token_ty, + &sm.generics, + MainTrait::Element(ty), + matches!(&field.stype, ShardableType::ISet(_)), + ) + } ShardableType::Bool | ShardableType::PersistentBool => { let token_ty = field_token_type(sm, field); token_trait_impls( @@ -1256,6 +1345,15 @@ fn traits_stream(sm: &SM, field: &Field) -> TokenStream { matches!(&field.stype, ShardableType::Map(_, _)), ) } + ShardableType::IMap(key, val) | ShardableType::PersistentIMap(key, val) => { + let token_ty = field_token_type(sm, field); + token_trait_impls( + &token_ty, + &sm.generics, + MainTrait::KeyValue(key, val), + matches!(&field.stype, ShardableType::IMap(_, _)), + ) + } ShardableType::Multiset(ty) => { let token_ty = field_token_type(sm, field); token_trait_impls(&token_ty, &sm.generics, MainTrait::Element(ty), false) @@ -1804,10 +1902,16 @@ fn field_token_collection_type(sm: &SM, field: &Field) -> Type { ShardableType::Map(key, val) | ShardableType::PersistentMap(key, val) => { Type::Verbatim(quote_vstd! { vstd => #vstd::tokens::MapToken<#key, #val, #tok> }) } + ShardableType::IMap(key, val) | ShardableType::PersistentIMap(key, val) => { + Type::Verbatim(quote_vstd! { vstd => #vstd::tokens::IMapToken<#key, #val, #tok> }) + } ShardableType::Set(t) | ShardableType::PersistentSet(t) => { Type::Verbatim(quote_vstd! { vstd => #vstd::tokens::SetToken<#t, #tok> }) } + ShardableType::ISet(t) | ShardableType::PersistentISet(t) => { + Type::Verbatim(quote_vstd! { vstd => #vstd::tokens::ISetToken<#t, #tok> }) + } ShardableType::Multiset(t) => { Type::Verbatim(quote_vstd! { vstd => #vstd::tokens::MultisetToken<#t, #tok> }) diff --git a/source/state_machines_macros/src/inherent_safety_conditions.rs b/source/state_machines_macros/src/inherent_safety_conditions.rs index 18f211c879..aec152575f 100644 --- a/source/state_machines_macros/src/inherent_safety_conditions.rs +++ b/source/state_machines_macros/src/inherent_safety_conditions.rs @@ -41,16 +41,21 @@ fn check_inherent_condition_for_special_op( ShardableType::Multiset(_) => CollectionType::Multiset, ShardableType::Option(_) => CollectionType::Option, ShardableType::Map(_, _) => CollectionType::Map, + ShardableType::IMap(_, _) => CollectionType::IMap, ShardableType::PersistentOption(_) => CollectionType::PersistentOption, ShardableType::PersistentMap(_, _) => CollectionType::PersistentMap, + ShardableType::PersistentIMap(_, _) => CollectionType::PersistentIMap, ShardableType::StorageOption(_) => CollectionType::Option, ShardableType::StorageMap(_, _) => CollectionType::Map, + ShardableType::StorageIMap(_, _) => CollectionType::IMap, ShardableType::Count => CollectionType::Nat, ShardableType::PersistentCount => CollectionType::PersistentNat, ShardableType::Bool => CollectionType::Bool, ShardableType::PersistentBool => CollectionType::PersistentBool, ShardableType::Set(_) => CollectionType::Set, + ShardableType::ISet(_) => CollectionType::ISet, ShardableType::PersistentSet(_) => CollectionType::PersistentSet, + ShardableType::PersistentISet(_) => CollectionType::PersistentISet, ShardableType::Variable(_) | ShardableType::Constant(_) @@ -89,7 +94,8 @@ fn check_inherent_condition_for_special_op( | CollectionType::Nat | CollectionType::PersistentNat | CollectionType::PersistentBool - | CollectionType::PersistentSet => { + | CollectionType::PersistentSet + | CollectionType::PersistentISet => { if user_gave_proof_body { let name = op.stmt.name(); let cname = coll_type.name(); @@ -106,9 +112,12 @@ fn check_inherent_condition_for_special_op( CollectionType::Option | CollectionType::PersistentOption | CollectionType::Map + | CollectionType::IMap | CollectionType::Set + | CollectionType::ISet | CollectionType::Bool - | CollectionType::PersistentMap => { + | CollectionType::PersistentMap + | CollectionType::PersistentIMap => { let name = op.stmt.name(); let type_name = coll_type.name(); if is_general { @@ -124,14 +133,18 @@ fn check_inherent_condition_for_special_op( #[derive(Copy, Clone)] enum CollectionType { Map, + IMap, PersistentMap, + PersistentIMap, Multiset, Option, PersistentOption, Nat, PersistentNat, Set, + ISet, PersistentSet, + PersistentISet, Bool, PersistentBool, } @@ -142,9 +155,13 @@ impl CollectionType { CollectionType::Nat => "count", CollectionType::PersistentNat => "persistent_count", CollectionType::Map => "map", + CollectionType::IMap => "imap", CollectionType::PersistentMap => "persistent_map", + CollectionType::PersistentIMap => "persistent_imap", CollectionType::Set => "set", + CollectionType::ISet => "iset", CollectionType::PersistentSet => "persistent_set", + CollectionType::PersistentISet => "persistent_iset", CollectionType::PersistentOption => "persistent_option", CollectionType::Multiset => "multiset", CollectionType::Option => "option", diff --git a/source/state_machines_macros/src/parse_token_stream.rs b/source/state_machines_macros/src/parse_token_stream.rs index 22c1b373f3..7b95d6868a 100644 --- a/source/state_machines_macros/src/parse_token_stream.rs +++ b/source/state_machines_macros/src/parse_token_stream.rs @@ -371,19 +371,24 @@ enum ShardingType { Option, Map, + IMap, Set, + ISet, Multiset, Count, Bool, PersistentOption, PersistentMap, + PersistentIMap, PersistentSet, + PersistentISet, PersistentCount, PersistentBool, StorageOption, StorageMap, + StorageIMap, } /// Get the sharding type from the attributes of the field. @@ -426,15 +431,20 @@ fn get_sharding_type( "constant" => ShardingType::Constant, "multiset" => ShardingType::Multiset, "set" => ShardingType::Set, + "iset" => ShardingType::ISet, "bool" => ShardingType::Bool, "count" => ShardingType::Count, "option" => ShardingType::Option, "map" => ShardingType::Map, + "imap" => ShardingType::IMap, "storage_option" => ShardingType::StorageOption, "storage_map" => ShardingType::StorageMap, + "storage_imap" => ShardingType::StorageIMap, "persistent_option" => ShardingType::PersistentOption, "persistent_map" => ShardingType::PersistentMap, + "persistent_imap" => ShardingType::PersistentIMap, "persistent_set" => ShardingType::PersistentSet, + "persistent_iset" => ShardingType::PersistentISet, "persistent_count" => ShardingType::PersistentCount, "persistent_bool" => ShardingType::PersistentBool, "not_tokenized" => ShardingType::NotTokenized, @@ -621,10 +631,18 @@ fn to_fields( let v = extract_template_params(&field.ty, "map", "Map", 2)?; ShardableType::Map(v[0].clone(), v[1].clone()) } + ShardingType::IMap => { + let v = extract_template_params(&field.ty, "imap", "IMap", 2)?; + ShardableType::IMap(v[0].clone(), v[1].clone()) + } ShardingType::Set => { let v = extract_template_params(&field.ty, "set", "Set", 1)?; ShardableType::Set(v[0].clone()) } + ShardingType::ISet => { + let v = extract_template_params(&field.ty, "iset", "ISet", 1)?; + ShardableType::ISet(v[0].clone()) + } ShardingType::StorageOption => { let v = extract_template_params(&field.ty, "storage_option", "Option", 1)?; ShardableType::StorageOption(v[0].clone()) @@ -633,6 +651,10 @@ fn to_fields( let v = extract_template_params(&field.ty, "storage_map", "Map", 2)?; ShardableType::StorageMap(v[0].clone(), v[1].clone()) } + ShardingType::StorageIMap => { + let v = extract_template_params(&field.ty, "storage_imap", "IMap", 2)?; + ShardableType::StorageIMap(v[0].clone(), v[1].clone()) + } ShardingType::PersistentOption => { let v = extract_template_params(&field.ty, "persistent_option", "Option", 1)?; ShardableType::PersistentOption(v[0].clone()) @@ -641,10 +663,18 @@ fn to_fields( let v = extract_template_params(&field.ty, "persistent_map", "Map", 2)?; ShardableType::PersistentMap(v[0].clone(), v[1].clone()) } + ShardingType::PersistentIMap => { + let v = extract_template_params(&field.ty, "persistent_imap", "IMap", 2)?; + ShardableType::PersistentIMap(v[0].clone(), v[1].clone()) + } ShardingType::PersistentSet => { let v = extract_template_params(&field.ty, "persistent_set", "Set", 1)?; ShardableType::PersistentSet(v[0].clone()) } + ShardingType::PersistentISet => { + let v = extract_template_params(&field.ty, "persistent_iset", "ISet", 1)?; + ShardableType::PersistentISet(v[0].clone()) + } }; field.ty = shardable_type_to_type(field.ty.span(), &stype); diff --git a/source/state_machines_macros/src/self_type_visitor.rs b/source/state_machines_macros/src/self_type_visitor.rs index d8d1edc90b..10f3d26c7f 100644 --- a/source/state_machines_macros/src/self_type_visitor.rs +++ b/source/state_machines_macros/src/self_type_visitor.rs @@ -45,17 +45,17 @@ fn replace_self_shardable_type(stype: &mut ShardableType, path: &Path) { ShardableType::PersistentOption(ty) => { replace_self_type(ty, path); } - ShardableType::Set(ty) => { + ShardableType::Set(ty) | ShardableType::ISet(ty) => { replace_self_type(ty, path); } - ShardableType::PersistentSet(ty) => { + ShardableType::PersistentSet(ty) | ShardableType::PersistentISet(ty) => { replace_self_type(ty, path); } - ShardableType::Map(key, val) => { + ShardableType::Map(key, val) | ShardableType::IMap(key, val) => { replace_self_type(key, path); replace_self_type(val, path); } - ShardableType::PersistentMap(key, val) => { + ShardableType::PersistentMap(key, val) | ShardableType::PersistentIMap(key, val) => { replace_self_type(key, path); replace_self_type(val, path); } @@ -65,7 +65,7 @@ fn replace_self_shardable_type(stype: &mut ShardableType, path: &Path) { ShardableType::StorageOption(ty) => { replace_self_type(ty, path); } - ShardableType::StorageMap(key, val) => { + ShardableType::StorageMap(key, val) | ShardableType::StorageIMap(key, val) => { replace_self_type(key, path); replace_self_type(val, path); } diff --git a/source/state_machines_macros/src/simplification.rs b/source/state_machines_macros/src/simplification.rs index 51719fb0bc..9a11a82bf6 100644 --- a/source/state_machines_macros/src/simplification.rs +++ b/source/state_machines_macros/src/simplification.rs @@ -552,11 +552,15 @@ fn expr_can_add(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Option None, MonoidElt::General(e) => match stype { ShardableType::PersistentSet(_) => None, + ShardableType::PersistentISet(_) => None, ShardableType::PersistentCount => None, ShardableType::PersistentBool => None, ShardableType::PersistentMap(_, _) => Some(Expr::Verbatim(quote! { (#cur).agrees(#e) })), + ShardableType::PersistentIMap(_, _) => Some(Expr::Verbatim(quote! { + (#cur).agrees(#e) + })), ShardableType::PersistentOption(_) => { let ty = get_opt_type(stype); Some(Expr::Verbatim(quote_vstd! { vstd => @@ -564,7 +568,7 @@ fn expr_can_add(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Option { - panic!("expr_can_add invalid case"); + panic!("expr_can_add invalid case {:?}", stype); } }, } @@ -586,18 +590,19 @@ fn expr_can_add(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Option { - Some(Expr::Verbatim(quote! { - (#cur).dom().disjoint((#e).dom()) - })) - } + ShardableType::Map(_, _) + | ShardableType::StorageMap(_, _) + | ShardableType::IMap(_, _) + | ShardableType::StorageIMap(_, _) => Some(Expr::Verbatim(quote! { + (#cur).dom().disjoint((#e).dom()) + })), ShardableType::Multiset(_) => None, ShardableType::Count => None, ShardableType::Bool => Some(Expr::Verbatim(quote! { (!(#cur) || !(#e)) })), - ShardableType::Set(_) => Some(Expr::Verbatim(quote! { + ShardableType::Set(_) | ShardableType::ISet(_) => Some(Expr::Verbatim(quote! { (#cur).disjoint(#e) })), @@ -632,7 +637,7 @@ fn expr_add(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Expr { }), MonoidElt::True => Expr::Verbatim(quote! { true }), MonoidElt::General(e) => match stype { - ShardableType::PersistentMap(_, _) => { + ShardableType::PersistentMap(_, _) | ShardableType::PersistentIMap(_, _) => { Expr::Verbatim(quote! { (#cur).union_prefer_right(#e) }) } ShardableType::PersistentOption(_) => { @@ -641,9 +646,11 @@ fn expr_add(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Expr { #vstd::state_machine_internal::opt_add::<#ty>(#cur, #e) }) } - ShardableType::PersistentSet(_) => Expr::Verbatim(quote! { - ((#cur).union(#e)) - }), + ShardableType::PersistentSet(_) | ShardableType::PersistentISet(_) => { + Expr::Verbatim(quote! { + ((#cur).union(#e)) + }) + } ShardableType::PersistentCount => Expr::Verbatim(quote_vstd! { vstd => #vstd::state_machine_internal::nat_max(#cur, #e) }), @@ -651,7 +658,7 @@ fn expr_add(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Expr { ((#cur) || (#e)) }), _ => { - panic!("expr_can_add invalid case"); + panic!("expr_can_add invalid case jonh1 {:?}", stype); } }, } @@ -689,17 +696,18 @@ fn expr_add(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Expr { }) } - ShardableType::Map(_, _) | ShardableType::StorageMap(_, _) => { - Expr::Verbatim(quote! { - (#cur).union_prefer_right(#e) - }) - } + ShardableType::Map(_, _) + | ShardableType::StorageMap(_, _) + | ShardableType::IMap(_, _) + | ShardableType::StorageIMap(_, _) => Expr::Verbatim(quote! { + (#cur).union_prefer_right(#e) + }), ShardableType::Multiset(_) => Expr::Verbatim(quote! { (#cur).add(#e) }), - ShardableType::Set(_) => Expr::Verbatim(quote! { + ShardableType::Set(_) | ShardableType::ISet(_) => Expr::Verbatim(quote! { ((#cur).union(#e)) }), @@ -712,7 +720,7 @@ fn expr_add(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Expr { }), _ => { - panic!("expected option/map/multiset"); + panic!("expected option/map/multiset (case 1)"); } }, } @@ -787,7 +795,10 @@ fn expr_ge(stype: &ShardableType, cur: &Expr, elt: &MonoidElt, pat_opt: &Option< ShardableType::Map(_, _) | ShardableType::PersistentMap(_, _) - | ShardableType::StorageMap(_, _) => Expr::Verbatim(quote! { + | ShardableType::IMap(_, _) + | ShardableType::PersistentIMap(_, _) + | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) => Expr::Verbatim(quote! { (#e).submap_of(#cur) }), @@ -795,7 +806,10 @@ fn expr_ge(stype: &ShardableType, cur: &Expr, elt: &MonoidElt, pat_opt: &Option< (#e).subset_of(#cur) }), - ShardableType::Set(_) | ShardableType::PersistentSet(_) => Expr::Verbatim(quote! { + ShardableType::Set(_) + | ShardableType::PersistentSet(_) + | ShardableType::ISet(_) + | ShardableType::PersistentISet(_) => Expr::Verbatim(quote! { (#e).subset_of(#cur) }), @@ -810,7 +824,7 @@ fn expr_ge(stype: &ShardableType, cur: &Expr, elt: &MonoidElt, pat_opt: &Option< } _ => { - panic!("expected option/map/multiset"); + panic!("expected option/map/multiset (case 2)"); } }, } @@ -845,7 +859,10 @@ fn expr_remove(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Expr { }) } - ShardableType::Map(_, _) | ShardableType::StorageMap(_, _) => Expr::Verbatim(quote! { + ShardableType::Map(_, _) + | ShardableType::StorageMap(_, _) + | ShardableType::IMap(_, _) + | ShardableType::StorageIMap(_, _) => Expr::Verbatim(quote! { (#cur).remove_keys(#e.dom()) }), @@ -853,7 +870,7 @@ fn expr_remove(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Expr { (#cur).sub(#e) }), - ShardableType::Set(_) => Expr::Verbatim(quote! { + ShardableType::Set(_) | ShardableType::ISet(_) => Expr::Verbatim(quote! { (#cur).difference(#e) }), @@ -866,7 +883,7 @@ fn expr_remove(stype: &ShardableType, cur: &Expr, elt: &MonoidElt) -> Expr { }), _ => { - panic!("expected option/map/multiset"); + panic!("expected option/map/multiset (case 3)"); } }, } diff --git a/source/state_machines_macros/src/to_token_stream.rs b/source/state_machines_macros/src/to_token_stream.rs index 3cf8511f49..22ab04f51e 100644 --- a/source/state_machines_macros/src/to_token_stream.rs +++ b/source/state_machines_macros/src/to_token_stream.rs @@ -1053,11 +1053,19 @@ pub fn shardable_type_to_type(span: Span, stype: &ShardableType) -> Type { ShardableType::Set(ty) | ShardableType::PersistentSet(ty) => { Type::Verbatim(quote_spanned_vstd! { vstd, span => #vstd::set::Set<#ty> }) } + ShardableType::ISet(ty) | ShardableType::PersistentISet(ty) => { + Type::Verbatim(quote_spanned_vstd! { vstd, span => #vstd::iset::ISet<#ty> }) + } ShardableType::Map(key, val) | ShardableType::PersistentMap(key, val) | ShardableType::StorageMap(key, val) => { Type::Verbatim(quote_spanned_vstd! { vstd, span => #vstd::map::Map<#key, #val> }) } + ShardableType::IMap(key, val) + | ShardableType::PersistentIMap(key, val) + | ShardableType::StorageIMap(key, val) => { + Type::Verbatim(quote_spanned_vstd! { vstd, span => #vstd::imap::IMap<#key, #val> }) + } ShardableType::Multiset(ty) => { Type::Verbatim(quote_spanned_vstd! { vstd, span => #vstd::multiset::Multiset<#ty> }) } diff --git a/source/state_machines_macros/src/transitions.rs b/source/state_machines_macros/src/transitions.rs index e1bc3f63d0..5346553f3a 100644 --- a/source/state_machines_macros/src/transitions.rs +++ b/source/state_machines_macros/src/transitions.rs @@ -254,12 +254,17 @@ fn is_allowed_in_update_in_normal_transition(stype: &ShardableType) -> bool { | ShardableType::Multiset(_) | ShardableType::Option(_) | ShardableType::Set(_) + | ShardableType::ISet(_) | ShardableType::Map(_, _) + | ShardableType::IMap(_, _) | ShardableType::StorageOption(_) | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) | ShardableType::PersistentMap(_, _) + | ShardableType::PersistentIMap(_, _) | ShardableType::PersistentOption(_) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::Count | ShardableType::PersistentCount | ShardableType::Bool @@ -288,15 +293,20 @@ fn is_allowed_in_special_op( } ShardableType::Map(_, _) + | ShardableType::IMap(_, _) | ShardableType::Option(_) | ShardableType::Set(_) + | ShardableType::ISet(_) | ShardableType::Bool | ShardableType::Multiset(_) | ShardableType::StorageOption(_) | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) | ShardableType::PersistentMap(_, _) + | ShardableType::PersistentIMap(_, _) | ShardableType::PersistentOption(_) | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) | ShardableType::PersistentBool | ShardableType::PersistentCount | ShardableType::Count => { @@ -385,14 +395,20 @@ fn op_matches_type(stype: &ShardableType, elt: &MonoidElt) -> bool { | ShardableType::NotTokenized(_) => false, ShardableType::Map(_, _) + | ShardableType::IMap(_, _) | ShardableType::PersistentMap(_, _) - | ShardableType::StorageMap(_, _) => match elt { + | ShardableType::PersistentIMap(_, _) + | ShardableType::StorageMap(_, _) + | ShardableType::StorageIMap(_, _) => match elt { MonoidElt::General(_) => true, MonoidElt::SingletonKV(_, _) => true, _ => false, }, - ShardableType::Set(_) | ShardableType::PersistentSet(_) => match elt { + ShardableType::Set(_) + | ShardableType::ISet(_) + | ShardableType::PersistentSet(_) + | ShardableType::PersistentISet(_) => match elt { MonoidElt::General(_) => true, MonoidElt::SingletonSet(_) => true, _ => false, diff --git a/source/vir/src/def.rs b/source/vir/src/def.rs index 6ea35c344e..56837283f7 100644 --- a/source/vir/src/def.rs +++ b/source/vir/src/def.rs @@ -1077,85 +1077,85 @@ pub fn fn_namespace_name(atomicity: InvAtomicity) -> Fun { }) } -pub fn set_type_path() -> Path { +pub fn iset_type_path() -> Path { Arc::new(PathX { krate: CrateId::Vstd, - segments: Arc::new(vec![Arc::new("set".to_string()), Arc::new("Set".to_string())]), + segments: Arc::new(vec![Arc::new("iset".to_string()), Arc::new("ISet".to_string())]), }) } -pub fn fn_set_empty_name() -> Fun { +pub fn fn_iset_empty_name() -> Fun { Arc::new(FunX { path: Arc::new(PathX { krate: CrateId::Vstd, segments: Arc::new(vec![ - Arc::new("set".to_string()), - Arc::new("Set".to_string()), + Arc::new("iset".to_string()), + Arc::new("ISet".to_string()), Arc::new("empty".to_string()), ]), }), }) } -pub fn fn_set_full_name() -> Fun { +pub fn fn_iset_full_name() -> Fun { Arc::new(FunX { path: Arc::new(PathX { krate: CrateId::Vstd, segments: Arc::new(vec![ - Arc::new("set".to_string()), - Arc::new("Set".to_string()), + Arc::new("iset".to_string()), + Arc::new("ISet".to_string()), Arc::new("full".to_string()), ]), }), }) } -pub fn fn_set_subset_of_name() -> Fun { +pub fn fn_iset_subset_of_name() -> Fun { Arc::new(FunX { path: Arc::new(PathX { krate: CrateId::Vstd, segments: Arc::new(vec![ - Arc::new("set".to_string()), - Arc::new("Set".to_string()), + Arc::new("iset".to_string()), + Arc::new("ISet".to_string()), Arc::new("subset_of".to_string()), ]), }), }) } -pub fn fn_set_insert_name() -> Fun { +pub fn fn_iset_insert_name() -> Fun { Arc::new(FunX { path: Arc::new(PathX { krate: CrateId::Vstd, segments: Arc::new(vec![ - Arc::new("set".to_string()), - Arc::new("Set".to_string()), + Arc::new("iset".to_string()), + Arc::new("ISet".to_string()), Arc::new("insert".to_string()), ]), }), }) } -pub fn fn_set_remove_name() -> Fun { +pub fn fn_iset_remove_name() -> Fun { Arc::new(FunX { path: Arc::new(PathX { krate: CrateId::Vstd, segments: Arc::new(vec![ - Arc::new("set".to_string()), - Arc::new("Set".to_string()), + Arc::new("iset".to_string()), + Arc::new("ISet".to_string()), Arc::new("remove".to_string()), ]), }), }) } -pub fn fn_set_contains_name() -> Fun { +pub fn fn_iset_contains_name() -> Fun { Arc::new(FunX { path: Arc::new(PathX { krate: CrateId::Vstd, segments: Arc::new(vec![ - Arc::new("set".to_string()), - Arc::new("Set".to_string()), + Arc::new("iset".to_string()), + Arc::new("ISet".to_string()), Arc::new("contains".to_string()), ]), }), diff --git a/source/vir/src/inv_masks.rs b/source/vir/src/inv_masks.rs index 3bc689a432..f94aa59ae2 100644 --- a/source/vir/src/inv_masks.rs +++ b/source/vir/src/inv_masks.rs @@ -9,7 +9,7 @@ use std::sync::Arc; /// The idea is to keep a "mask" at every program point, the set of invariants /// which are allowed to be opened. /// -/// In general, the mask set is represented by a vstd::set::Set. As an optimization, +/// In general, the mask set is represented by a vstd::iset::ISet. As an optimization, /// we keep track of whether the mask is empty or full, and elide certain checks in /// those cases. @@ -35,9 +35,9 @@ pub fn namespace_set_typs() -> Typs { Arc::new(vec![namespace_id_typ()]) } -pub fn namespace_set_typ(_ctx: &Ctx) -> Typ { +pub fn namespace_set_typ() -> Typ { Arc::new(TypX::Datatype( - Dt::Path(crate::def::set_type_path()), + Dt::Path(crate::def::iset_type_path()), namespace_set_typs(), Arc::new(vec![]), )) @@ -47,37 +47,35 @@ impl MaskSet { pub fn to_exp(self: &Self, ctx: &Ctx) -> Exp { match self { MaskSet::Empty { span } => { - let empty_fun = CallFun::Fun(crate::def::fn_set_empty_name(), None); + let empty_fun = CallFun::Fun(crate::def::fn_iset_empty_name(), None); let empty_expx = ExpX::Call(empty_fun, namespace_set_typs(), Arc::new(vec![])); - let empty_exp = SpannedTyped::new(&span, &namespace_set_typ(ctx), empty_expx); + let empty_exp = SpannedTyped::new(&span, &namespace_set_typ(), empty_expx); empty_exp } MaskSet::Full { span } => { - let full_fun = CallFun::Fun(crate::def::fn_set_full_name(), None); + let full_fun = CallFun::Fun(crate::def::fn_iset_full_name(), None); let full_expx = ExpX::Call(full_fun, namespace_set_typs(), Arc::new(vec![])); - let full_exp = SpannedTyped::new(&span, &namespace_set_typ(ctx), full_expx); + let full_exp = SpannedTyped::new(&span, &namespace_set_typ(), full_expx); full_exp } MaskSet::Insert { base, elem } => { - let insert_fun = CallFun::Fun(crate::def::fn_set_insert_name(), None); + let insert_fun = CallFun::Fun(crate::def::fn_iset_insert_name(), None); let insert_expx = ExpX::Call( insert_fun, namespace_set_typs(), Arc::new(vec![base.to_exp(ctx), elem.clone()]), ); - let insert_exp = - SpannedTyped::new(&elem.span, &namespace_set_typ(ctx), insert_expx); + let insert_exp = SpannedTyped::new(&elem.span, &namespace_set_typ(), insert_expx); insert_exp } MaskSet::Remove { base, elem } => { - let remove_fun = CallFun::Fun(crate::def::fn_set_remove_name(), None); + let remove_fun = CallFun::Fun(crate::def::fn_iset_remove_name(), None); let remove_expx = ExpX::Call( remove_fun, namespace_set_typs(), Arc::new(vec![base.to_exp(ctx), elem.clone()]), ); - let remove_exp = - SpannedTyped::new(&elem.span, &namespace_set_typ(ctx), remove_expx); + let remove_exp = SpannedTyped::new(&elem.span, &namespace_set_typ(), remove_expx); remove_exp } MaskSet::Arbitrary { set } => set.clone(), @@ -155,7 +153,7 @@ impl MaskSet { asserts } _ => { - let contains_fun = CallFun::Fun(crate::def::fn_set_contains_name(), None); + let contains_fun = CallFun::Fun(crate::def::fn_iset_contains_name(), None); let contains_expx = ExpX::Call( contains_fun, namespace_set_typs(), @@ -229,7 +227,7 @@ impl MaskSet { asserts } _ => { - let subset_of_fun = CallFun::Fun(crate::def::fn_set_subset_of_name(), None); + let subset_of_fun = CallFun::Fun(crate::def::fn_iset_subset_of_name(), None); let self_exp = self.to_exp(ctx); let other_exp = other.to_exp(ctx); let subset_of_expx = ExpX::Call( diff --git a/source/vir/src/prune.rs b/source/vir/src/prune.rs index 4fc31e9686..fe07d4e16c 100644 --- a/source/vir/src/prune.rs +++ b/source/vir/src/prune.rs @@ -12,9 +12,9 @@ use crate::ast_util::{is_body_visible_to, is_visible_to, is_visible_to_or_true}; use crate::ast_visitor::{VisitorControlFlow, VisitorScopeMap}; use crate::datatype_to_air::is_datatype_transparent; use crate::def::{ - Spanned, fn_array_update, fn_inv_name, fn_namespace_name, fn_set_contains_name, - fn_set_empty_name, fn_set_full_name, fn_set_insert_name, fn_set_remove_name, - fn_set_subset_of_name, fn_slice_index, fn_slice_len, fn_slice_update, + Spanned, fn_array_update, fn_inv_name, fn_iset_contains_name, fn_iset_empty_name, + fn_iset_full_name, fn_iset_insert_name, fn_iset_remove_name, fn_iset_subset_of_name, + fn_namespace_name, fn_slice_index, fn_slice_len, fn_slice_update, }; use crate::poly::MonoTyp; use crate::resolve_axioms::{ResolvableType, ResolvedTypeCollection}; @@ -440,12 +440,12 @@ fn traverse_reachable(ctxt: &Ctxt, state: &mut State) { // set operations may be invoked for checking invariant masks, // either when opening an invariant or invoking another function. let reach_set_ops = |state: &mut State| { - reach_function(ctxt, state, &fn_set_contains_name()); - reach_function(ctxt, state, &fn_set_empty_name()); - reach_function(ctxt, state, &fn_set_full_name()); - reach_function(ctxt, state, &fn_set_insert_name()); - reach_function(ctxt, state, &fn_set_remove_name()); - reach_function(ctxt, state, &fn_set_subset_of_name()); + reach_function(ctxt, state, &fn_iset_contains_name()); + reach_function(ctxt, state, &fn_iset_empty_name()); + reach_function(ctxt, state, &fn_iset_full_name()); + reach_function(ctxt, state, &fn_iset_insert_name()); + reach_function(ctxt, state, &fn_iset_remove_name()); + reach_function(ctxt, state, &fn_iset_subset_of_name()); }; let maybe_reach_set_ops_for_call = |state: &mut State, callee_name: &Fun| { let caller = diff --git a/source/vstd/cell.rs b/source/vstd/cell.rs index ef73211080..d0363fe0d7 100644 --- a/source/vstd/cell.rs +++ b/source/vstd/cell.rs @@ -6,11 +6,11 @@ use core::marker; use core::{mem, mem::MaybeUninit}; use super::invariant::*; +use super::iset::*; use super::modes::*; use super::pervasive::*; use super::prelude::*; pub use super::raw_ptr::MemContents; -use super::set::*; use super::*; pub mod invcell; @@ -21,7 +21,7 @@ pub mod pcell_maybe_uninit; verus! { -broadcast use {super::map::group_map_axioms, super::set::group_set_axioms}; +broadcast use {super::imap::group_imap_lemmas, super::iset::group_iset_lemmas}; /// **Now deprecated** See [`pcell::PCell`] or [`pcell_maybe_uninit::PCell`] instead /// @@ -310,8 +310,8 @@ impl PCell { struct InvCellPred {} -impl InvariantPredicate<(Set, PCell), PointsTo> for InvCellPred { - closed spec fn inv(k: (Set, PCell), perm: PointsTo) -> bool { +impl InvariantPredicate<(ISet, PCell), PointsTo> for InvCellPred { + closed spec fn inv(k: (ISet, PCell), perm: PointsTo) -> bool { let (possible_values, pcell) = k; { &&& perm.is_init() @@ -325,9 +325,9 @@ impl InvariantPredicate<(Set, PCell), PointsTo> for InvCellPred { #[cfg_attr(not(verus_verify_core), deprecated = "use `vstd::cell::invcell::InvCell` instead")] #[verifier::reject_recursive_types(T)] pub struct InvCell { - possible_values: Ghost>, + possible_values: Ghost>, pcell: PCell, - perm_inv: Tracked, PCell), PointsTo, InvCellPred>>, + perm_inv: Tracked, PCell), PointsTo, InvCellPred>>, } #[cfg_attr(not(verus_verify_core), deprecated = "use `vstd::cell::invcell::InvCell` instead")] @@ -348,7 +348,7 @@ impl InvCell { forall|v| f(v) <==> cell.inv(v), { let (pcell, Tracked(perm)) = PCell::new(val); - let ghost possible_values = Set::new(f); + let ghost possible_values = ISet::new(f); let tracked perm_inv = LocalInvariant::new((possible_values, pcell), perm, 0); InvCell { possible_values: Ghost(possible_values), pcell, perm_inv: Tracked(perm_inv) } } diff --git a/source/vstd/imap.rs b/source/vstd/imap.rs new file mode 100644 index 0000000000..bfda5bbbc1 --- /dev/null +++ b/source/vstd/imap.rs @@ -0,0 +1,495 @@ +#![allow(unused_imports)] + +use super::pervasive::*; +use super::prelude::*; +use super::set::*; + +use verus as verus_; // skip verusfmt due to unhandled return-value-pattern +verus_! { + +/// `IMap` is an abstract map type for specifications. +/// +/// An object `map: IMap` has a _domain_, a set of keys given by [`map.dom()`](IMap::dom), +/// and a mapping for keys in the domain to values, given by [`map[key]`](IMap::index). +/// Alternatively, a map can be thought of as a set of `(K, V)` pairs where each key +/// appears in at most entry. +/// +/// In general, a map might be infinite. +/// To work specifically with finite maps, use `Map`. +/// +/// IMaps can be constructed in a few different ways: +/// * [`IMap::empty()`] constructs an empty map. +/// * [`IMap::new`] and [`IMap::total`] construct a map given functions that specify its domain and the mapping +/// from keys to values (a _map comprehension_). +/// * The [`imap!`] macro, to construct small maps of a fixed size. +/// * By manipulating an existing map with [`IMap::insert`] or [`IMap::remove`]. +/// +/// To prove that two maps are equal, it is usually easiest to use the extensionality operator `=~=`. +#[verifier::ext_equal] +#[verifier::reject_recursive_types(K)] +#[verifier::accept_recursive_types(V)] +pub tracked struct IMap { + mapping: spec_fn(K) -> Option, +} + +impl IMap { + /// An empty map. + pub closed spec fn empty() -> IMap { + IMap { mapping: |k| None } + } + + /// Gives a `IMap` whose domain contains every key, and maps each key + /// to the value given by `fv`. + pub open spec fn total(fv: spec_fn(K) -> V) -> IMap { + ISet::full().mk_map(fv) + } + + /// Gives a `IMap` whose domain is given by the boolean predicate on keys `fk`, + /// and maps each key to the value given by `fv`. + pub open spec fn new(fk: spec_fn(K) -> bool, fv: spec_fn(K) -> V) -> IMap { + ISet::new(fk).mk_map(fv) + } + + /// The domain of the map as a set. + pub closed spec fn dom(self) -> ISet { + ISet::new(|k| (self.mapping)(k) is Some) + } + + /// Gets the value that the given key `key` maps to. + /// For keys not in the domain, the result is meaningless and arbitrary. + pub closed spec fn index(self, key: K) -> V + recommends + self.dom().contains(key), + { + (self.mapping)(key)->Some_0 + } + + /// `[]` operator, synonymous with `index` + #[verifier::inline] + pub open spec fn spec_index(self, key: K) -> V + recommends + self.dom().contains(key), + { + self.index(key) + } + + /// Inserts the given (key, value) pair into the map. + /// + /// If the key is already present from the map, then its existing value is overwritten + /// by the new value. + pub closed spec fn insert(self, key: K, value: V) -> IMap { + IMap { + mapping: |k| + if k == key { + Some(value) + } else { + (self.mapping)(k) + }, + } + } + + /// Removes the given key and its associated value from the map. + /// + /// If the key is already absent from the map, then the map is left unchanged. + pub closed spec fn remove(self, key: K) -> IMap { + IMap { + mapping: |k| + if k == key { + None + } else { + (self.mapping)(k) + }, + } + } + + /// Returns the number of key-value pairs in the map + pub open spec fn len(self) -> nat { + self.dom().len() + } + + /// Create an empty tracked map. + /// + /// This allows us to create a map, which we know is empty, that is _tracked_. + pub axiom fn tracked_empty() -> (tracked out_v: Self) + ensures + out_v == IMap::::empty(), + ; + + /// Inserts the given `(key, tracked value)` pair into the map. + /// + /// If the key is already present from the map, then its existing value is overwritten + /// by the new value. + pub axiom fn tracked_insert(tracked &mut self, key: K, tracked value: V) + ensures + *final(self) == IMap::insert(*old(self), key, value), + ; + + /// Removes the given key and its associated _tracked_ value from the map. + /// + /// The key must exist in the map + pub axiom fn tracked_remove(tracked &mut self, key: K) -> (tracked v: V) + requires + old(self).dom().contains(key), + ensures + *final(self) == IMap::remove(*old(self), key), + v == old(self)[key], + ; + + /// Index into a tracked map, getting a tracked borrow of the value + pub axiom fn tracked_borrow(tracked &self, key: K) -> (tracked v: &V) + requires + self.dom().contains(key), + ensures + *v === self.index(key), + ; + + /// Index into a tracked map, getting a tracked mutable borrow of the value + pub axiom fn tracked_borrow_mut(tracked &mut self, key: K) -> (tracked v: &mut V) + requires + self.dom().contains(key), + ensures + *v === old(self).index(key), + *final(self) === old(self).insert(key, *final(v)) + ; + + /// Split a mutable borrow of a map into two. + pub axiom fn tracked_borrow_mut_split(tracked &mut self, keys: ISet) + -> (tracked (m1, m2): (&mut Self, &mut Self)) + requires + keys <= self.dom(), + ensures + *m1 == old(self).restrict(keys), + *m2 == old(self).remove_keys(keys), + *final(self) == final(m1).union_prefer_right(*final(m2)), + ; + + /// Change the keys of a map, by reverse lookup in a different map. + /// + /// For each `(old_key, new_key)` pair in `key_map`, the new map will have `(new_key, old_map[old_key])`. + /// Note the new map may be smaller than the old map if the `key_map` omits mappings for some of the old keys. + pub axiom fn tracked_map_keys( + tracked old_map: IMap, + key_map: IMap, + ) -> (tracked new_map: IMap) + requires + forall|j| #![auto] key_map.contains_key(j) ==> old_map.contains_key(key_map[j]), + forall|j1, j2| + #![auto] + j1 != j2 && key_map.contains_key(j1) && key_map.contains_key(j2) ==> key_map[j1] + != key_map[j2], + ensures + new_map.dom() == key_map.dom(), + forall|j| + key_map.contains_key(j) ==> new_map.contains_key(j) && #[trigger] new_map[j] + == old_map[key_map[j]], + ; + + /// Extract a set of keys (and their corresponding values) out of the map. + /// + /// This allows us to split a map based on a subset of the domain. + pub axiom fn tracked_remove_keys(tracked &mut self, keys: ISet) -> (tracked out_map: IMap< + K, + V, + >) + requires + keys.subset_of(old(self).dom()), + ensures + *final(self) == old(self).remove_keys(keys), + out_map == old(self).restrict(keys), + ; + + /// Merge a map into a tracked map. + /// + /// The new (key, value) pairs take precendece. + pub axiom fn tracked_union_prefer_right(tracked &mut self, right: Self) + ensures + *final(self) == old(self).union_prefer_right(right), + ; +} + +// Trusted axioms +/* REVIEW: this is simpler than the two separate axioms below -- would this be ok? +pub broadcast axiom fn axiom_imap_index_decreases(m: IMap, key: K) + requires + m.dom().contains(key), + ensures + #[trigger](decreases_to!(m => m[key])); +*/ + +pub broadcast axiom fn axiom_imap_index_decreases_finite(m: IMap, key: K) + requires + m.dom().finite(), + m.dom().contains(key), + ensures + #[trigger] (decreases_to!(m => m[key])), +; + +// REVIEW: this is currently a special case that is hard-wired into the verifier +// It implements a version of https://github.com/FStarLang/FStar/pull/2954 . +pub broadcast axiom fn axiom_imap_index_decreases_infinite(m: IMap, key: K) + requires + m.dom().contains(key), + ensures + #[trigger] is_smaller_than_recursive_function_field(m[key], m), +; + +/// The domain of the empty map is the empty set +pub broadcast proof fn lemma_imap_empty() + ensures + #[trigger] IMap::::empty().dom() == ISet::::empty(), +{ + broadcast use super::iset::group_iset_lemmas; + + assert(ISet::new(|k: K| (|k| None::)(k) is Some) == ISet::::empty()); +} + +/// The domain of a map after inserting a key-value pair is equivalent to inserting the key into +/// the original map's domain set. +pub broadcast proof fn lemma_imap_insert_domain(m: IMap, key: K, value: V) + ensures + #[trigger] m.insert(key, value).dom() == m.dom().insert(key), +{ + broadcast use super::iset::group_iset_lemmas; + + assert(m.insert(key, value).dom() =~= m.dom().insert(key)); +} + +/// Inserting `value` at `key` in `m` results in a map that maps `key` to `value` +pub broadcast proof fn lemma_imap_insert_same(m: IMap, key: K, value: V) + ensures + #[trigger] m.insert(key, value)[key] == value, +{ +} + +/// Inserting `value` at `key2` does not change the value mapped to by any other keys in `m` +pub broadcast proof fn lemma_imap_insert_different(m: IMap, key1: K, key2: K, value: V) + requires + key1 != key2, + ensures + #[trigger] m.insert(key2, value)[key1] == m[key1], +{ +} + +/// The domain of a map after removing a key-value pair is equivalent to removing the key from +/// the original map's domain set. +pub broadcast proof fn lemma_imap_remove_domain(m: IMap, key: K) + ensures + #[trigger] m.remove(key).dom() == m.dom().remove(key), +{ + broadcast use super::iset::group_iset_lemmas; + + assert(m.remove(key).dom() =~= m.dom().remove(key)); +} + +/// Removing a key-value pair from a map does not change the value mapped to by +/// any other keys in the map. +pub broadcast proof fn lemma_imap_remove_different(m: IMap, key1: K, key2: K) + requires + key1 != key2, + ensures + #[trigger] m.remove(key2)[key1] == m[key1], +{ +} + +/// Two maps are equivalent if their domains are equivalent and every key in their domains map to the same value. +pub broadcast proof fn lemma_imap_ext_equal(m1: IMap, m2: IMap) + ensures + #[trigger] (m1 =~= m2) <==> { + &&& m1.dom() =~= m2.dom() + &&& forall|k: K| #![auto] m1.dom().contains(k) ==> m1[k] == m2[k] + }, +{ + broadcast use super::iset::group_iset_lemmas; + + if m1 =~= m2 { + assert(m1.dom() =~= m2.dom()); + assert(forall|k: K| #![auto] m1.dom().contains(k) ==> m1[k] == m2[k]); + } + if ({ + &&& m1.dom() =~= m2.dom() + &&& forall|k: K| #![auto] m1.dom().contains(k) ==> m1[k] == m2[k] + }) { + if m1.mapping != m2.mapping { + assert(exists|k| #[trigger] (m1.mapping)(k) != (m2.mapping)(k)); + let k = choose|k| #[trigger] (m1.mapping)(k) != (m2.mapping)(k); + if m1.dom().contains(k) { + assert(m1[k] == m2[k]); + } + assert(false); + } + assert(m1 =~= m2); + } +} + +pub broadcast proof fn lemma_imap_ext_equal_deep(m1: IMap, m2: IMap) + ensures + #[trigger] (m1 =~~= m2) <==> { + &&& m1.dom() =~~= m2.dom() + &&& forall|k: K| #![auto] m1.dom().contains(k) ==> m1[k] =~~= m2[k] + }, +{ + lemma_imap_ext_equal(m1, m2); +} + +pub broadcast group group_imap_lemmas { + axiom_imap_index_decreases_finite, + axiom_imap_index_decreases_infinite, + lemma_imap_empty, + lemma_imap_insert_domain, + lemma_imap_insert_same, + lemma_imap_insert_different, + lemma_imap_remove_domain, + lemma_imap_remove_different, + lemma_imap_ext_equal, + lemma_imap_ext_equal_deep, +} + +// Macros +#[doc(hidden)] +#[macro_export] +macro_rules! imap_internal { + [$($key:expr => $value:expr),* $(,)?] => { + $crate::vstd::imap::IMap::empty() + $(.insert($key, $value))* + } +} + +/// Create a map using syntax like `imap![key1 => val1, key2 => val, ...]`. +/// +/// This is equivalent to `IMap::empty().insert(key1, val1).insert(key2, val2)...`. +/// +/// Note that this does _not_ require all keys to be distinct. In the case that two +/// or more keys are equal, the resulting map uses the value of the rightmost entry. +#[macro_export] +macro_rules! imap { + [$($tail:tt)*] => { + $crate::vstd::prelude::verus_proof_macro_exprs!($crate::vstd::imap::imap_internal!($($tail)*)) + }; +} + +#[doc(hidden)] +#[verifier::inline] +pub open spec fn check_argument_is_map(m: IMap) -> IMap { + m +} + +#[doc(hidden)] +pub use imap_internal; +pub use imap; + +/// Prove two maps `map1` and `map2` are equal by proving that their values are equal at each key. +/// +/// More precisely, `assert_imaps_equal!` requires that for each key `k`: +/// * `map1` contains `k` in its domain if and only if `map2` does (`map1.dom().contains(k) <==> map2.dom().contains(k)`) +/// * If they contain `k` in their domains, then their values are equal (`map1.dom().contains(k) && map2.dom().contains(k) ==> map1[k] == map2[k]`) +/// +/// The property that equality follows from these facts is often called _extensionality_. +/// +/// `assert_imaps_equal!` can handle many trivial-looking +/// identities without any additional help: +/// +/// ```rust +/// proof fn insert_remove(m: IMap, k: int, v: int) +/// requires !m.dom().contains(k) +/// ensures m.insert(k, v).remove(k) == m +/// { +/// let m2 = m.insert(k, v).remove(k); +/// assert_imaps_equal!(m == m2); +/// assert(m == m2); +/// } +/// ``` +/// +/// For more complex cases, a proof may be required for each key: +/// +/// ```rust +/// proof fn bitvector_maps() { +/// let m1 = IMap::::new( +/// |key: u64| key & 31 == key, +/// |key: u64| key | 5); +/// +/// let m2 = IMap::::new( +/// |key: u64| key < 32, +/// |key: u64| 5 | key); +/// +/// assert_imaps_equal!(m1 == m2, key => { +/// // Show that the domains of m1 and m2 are the same by showing their predicates +/// // are equivalent. +/// assert_bit_vector((key & 31 == key) <==> (key < 32)); +/// +/// // Show that the values are the same by showing that these expressions +/// // are equivalent. +/// assert_bit_vector(key | 5 == 5 | key); +/// }); +/// } +/// ``` +#[macro_export] +macro_rules! assert_imaps_equal { + [$($tail:tt)*] => { + $crate::vstd::prelude::verus_proof_macro_exprs!($crate::vstd::imap::assert_imaps_equal_internal!($($tail)*)) + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! assert_imaps_equal_internal { + (::verus_builtin::spec_eq($m1:expr, $m2:expr)) => { + assert_imaps_equal_internal!($m1, $m2) + }; + (::verus_builtin::spec_eq($m1:expr, $m2:expr), $k:ident $( : $t:ty )? => $bblock:block) => { + assert_imaps_equal_internal!($m1, $m2, $k $( : $t )? => $bblock) + }; + ($m1:expr, $m2:expr $(,)?) => { + assert_imaps_equal_internal!($m1, $m2, key => { }) + }; + ($m1:expr, $m2:expr, $k:ident $( : $t:ty )? => $bblock:block) => { + #[verifier::spec] let m1 = $crate::vstd::imap::check_argument_is_map($m1); + #[verifier::spec] let m2 = $crate::vstd::imap::check_argument_is_map($m2); + $crate::vstd::prelude::assert_by($crate::vstd::prelude::equal(m1, m2), { + $crate::vstd::prelude::assert_forall_by(|$k $( : $t )?| { + // TODO better error message here: show the individual conjunct that fails, + // and maybe give an error message in english as well + $crate::vstd::prelude::ensures([ + $crate::vstd::prelude::imply(#[verifier::trigger] m1.dom().contains($k), m2.dom().contains($k)) + && $crate::vstd::prelude::imply(m2.dom().contains($k), m1.dom().contains($k)) + && $crate::vstd::prelude::imply(m1.dom().contains($k) && m2.dom().contains($k), + $crate::vstd::prelude::equal(m1.index($k), m2.index($k))) + ]); + { $bblock } + }); + $crate::vstd::prelude::assert_($crate::vstd::prelude::ext_equal(m1, m2)); + }); + } +} + +#[doc(hidden)] +pub use assert_imaps_equal_internal; +pub use assert_imaps_equal; + +} // verus! + +verus_! { // skip verusfmt, issue with 'final' + +impl IMap { + pub proof fn tracked_map_keys_in_place(tracked &mut self, key_map: IMap) + requires + forall|j| + #![auto] + key_map.dom().contains(j) ==> old(self).dom().contains(key_map.index(j)), + forall|j1, j2| + #![auto] + j1 != j2 && key_map.dom().contains(j1) && key_map.dom().contains(j2) + ==> key_map.index(j1) != key_map.index(j2), + ensures + forall|j| #[trigger] final(self).dom().contains(j) == key_map.dom().contains(j), + forall|j| + key_map.dom().contains(j) ==> final(self).dom().contains(j) && #[trigger] final(self).index(j) + == old(self).index(key_map.index(j)), + { + let tracked mut tmp = Self::tracked_empty(); + super::modes::tracked_swap(&mut tmp, self); + let tracked mut tmp = Self::tracked_map_keys(tmp, key_map); + super::modes::tracked_swap(&mut tmp, self); + } +} + +} // verus! diff --git a/source/vstd/imap_lib.rs b/source/vstd/imap_lib.rs new file mode 100644 index 0000000000..8b0b9ef0d7 --- /dev/null +++ b/source/vstd/imap_lib.rs @@ -0,0 +1,827 @@ +#[macro_use] +use super::imap::{IMap, assert_imaps_equal, assert_imaps_equal_internal}; +#[cfg(verus_keep_ghost)] +use super::iset::*; +#[cfg(verus_keep_ghost)] +use super::iset_lib::*; +#[allow(unused_imports)] +use super::pervasive::*; +#[allow(unused_imports)] +use super::prelude::*; +#[allow(unused_imports)] +use super::relations::*; + +verus! { + +broadcast use {super::imap::group_imap_lemmas, super::iset::group_iset_lemmas}; + +impl IMap { + /// Is `true` if called by a "full" map, i.e., a map containing every element of type `A`. + #[verifier::inline] + pub open spec fn is_full(self) -> bool { + self.dom().is_full() + } + + /// Is `true` if called by an "empty" map, i.e., a map containing no elements and has length 0 + #[verifier::inline] + pub open spec fn is_empty(self) -> (b: bool) { + self.dom().is_empty() + } + + /// Returns true if the key `k` is in the domain of `self`. + #[verifier::inline] + pub open spec fn contains_key(self, k: K) -> bool { + self.dom().contains(k) + } + + /// Returns true if the value `v` is in the range of `self`. + pub open spec fn contains_value(self, v: V) -> bool { + exists|i: K| #[trigger] self.dom().contains(i) && self[i] == v + } + + /// Returns `Some(v)` if the key `k` is in the domain of `self` and maps to `v`, + /// and `None` if the key `k` is not in the domain of `self`. + pub open spec fn index_opt(self, k: K) -> Option { + if self.contains_key(k) { + Some(self[k]) + } else { + None + } + } + + /// + /// Returns the set of values in the map. + /// + /// ## Example + /// + /// ```rust + /// let m: IMap = imap![1 => 10, 2 => 11]; + /// assert(m.values() == iset![10int, 11int]) by { + /// assert(m.contains_key(1)); + /// assert(m.contains_key(2)); + /// assert(m.values() =~= iset![10int, 11int]); + /// } + /// ``` + pub open spec fn values(self) -> ISet { + ISet::::new(|v: V| self.contains_value(v)) + } + + /// + /// Returns the set of key-value pairs representing the map + /// + /// ## Example + /// + /// ```rust + /// let m: IMap = imap![1 => 10, 2 => 11]; + /// assert(m.kv_pairs() == iset![(1int, 10int), (2int, 11int)] by { + /// assert(m.contains_pair(1int, 10int)); + /// assert(m.contains_pair(2int, 11int)); + /// assert(m.kv_pairs() =~= iset![(1int, 10int), (2int, 11int)]); + /// } + /// ``` + pub open spec fn kv_pairs(self) -> ISet<(K, V)> { + ISet::<(K, V)>::new(|kv: (K, V)| self.dom().contains(kv.0) && self[kv.0] == kv.1) + } + + /// Returns true if the key `k` is in the domain of `self`, and it maps to the value `v`. + pub open spec fn contains_pair(self, k: K, v: V) -> bool { + self.dom().contains(k) && self[k] == v + } + + /// Returns true if `m1` is _contained in_ `m2`, i.e., the domain of `m1` is a subset + /// of the domain of `m2`, and they agree on all values in `m1`. + /// + /// ## Example + /// + /// ```rust + /// assert( + /// imap![1 => 10, 2 => 11].le(imap![1 => 10, 2 => 11, 3 => 12]) + /// ); + /// ``` + pub open spec fn submap_of(self, m2: Self) -> bool { + forall|k: K| #[trigger] + self.dom().contains(k) ==> #[trigger] m2.dom().contains(k) && self[k] == m2[k] + } + + #[verifier::inline] + pub open spec fn spec_le(self, m2: Self) -> bool { + self.submap_of(m2) + } + + /// Gives the union of two maps, defined as: + /// * The domain is the union of the two input maps. + /// * For a given key in _both_ input maps, it maps to the same value that it maps to in the _right_ map (`m2`). + /// * For any other key in either input map (but not both), it maps to the same value + /// as it does in that map. + /// + /// ## Example + /// + /// ```rust + /// assert( + /// map![1 => 10, 2 => 11].union_prefer_right(map![1 => 20, 3 => 13]) + /// =~= map![1 => 20, 2 => 11, 3 => 13]); + /// ``` + pub open spec fn union_prefer_right(self, m2: Self) -> Self { + Self::new( + |k: K| self.dom().contains(k) || m2.dom().contains(k), + |k: K| + if m2.dom().contains(k) { + m2[k] + } else { + self[k] + }, + ) + } + + /// Removes the given keys and their associated values from the map. + /// + /// Ignores any key in `keys` which is not in the domain of `self`. + /// + /// ## Example + /// + /// ```rust + /// assert( + /// imap![1 => 10, 2 => 11, 3 => 12].remove_keys(iset!{2, 3, 4}) + /// =~= imap![1 => 10]); + /// ``` + pub open spec fn remove_keys(self, keys: ISet) -> Self { + Self::new(|k: K| self.dom().contains(k) && !keys.contains(k), |k: K| self[k]) + } + + /// Complement to `remove_keys`. Restricts the map to (key, value) pairs + /// for keys that are _in_ the given set; that is, it removes any keys + /// _not_ in the set. + /// + /// ## Example + /// + /// ```rust + /// assert( + /// map![1 => 10, 2 => 11, 3 => 12].remove_keys(set!{2, 3, 4}) + /// =~= map![2 => 11, 3 => 12]); + /// ``` + pub open spec fn restrict(self, keys: ISet) -> Self { + Self::new(|k: K| self.dom().contains(k) && keys.contains(k), |k: K| self[k]) + } + + /// Returns `true` if and only if the given key maps to the same value or does not exist in self and m2. + pub open spec fn is_equal_on_key(self, m2: Self, key: K) -> bool { + ||| (!self.dom().contains(key) && !m2.dom().contains(key)) + ||| (self.dom().contains(key) && m2.dom().contains(key) && self[key] == m2[key]) + } + + /// Returns `true` if the two given maps agree on all keys that their domains share + pub open spec fn agrees(self, m2: Self) -> bool { + forall|k| #![auto] self.dom().contains(k) && m2.dom().contains(k) ==> self[k] == m2[k] + } + + /// Map a function `f` over all (k, v) pairs in `self`. + pub open spec fn map_entries(self, f: spec_fn(K, V) -> W) -> IMap { + IMap::new(|k: K| self.contains_key(k), |k: K| f(k, self[k])) + } + + /// Map a function `f` over the values in `self`. + pub open spec fn map_values(self, f: spec_fn(V) -> W) -> IMap { + IMap::new(|k: K| self.contains_key(k), |k: K| f(self[k])) + } + + /// Returns `true` if and only if a map is injective + pub open spec fn is_injective(self) -> bool { + forall|x: K, y: K| + x != y && self.dom().contains(x) && self.dom().contains(y) ==> #[trigger] self[x] + != #[trigger] self[y] + } + + /// Swaps map keys and values. Values are not required to be unique; no + /// promises on which key is chosen on the intersection. + pub open spec fn invert(self) -> IMap { + IMap::::new( + |v: V| self.contains_value(v), + |v: V| choose|k: K| self.contains_pair(k, v), + ) + } + + // Proven lemmas + /// Removing a key from a map that previously contained that key decreases + /// the map's length by one + pub proof fn lemma_remove_key_len(self, key: K) + requires + self.dom().contains(key), + self.dom().finite(), + ensures + self.dom().len() == 1 + self.remove(key).dom().len(), + { + } + + /// The domain of a map after removing a key is equivalent to removing the key from + /// the domain of the original map. + pub proof fn lemma_remove_equivalency(self, key: K) + ensures + self.remove(key).dom() == self.dom().remove(key), + { + } + + /// Removing a set of n keys from a map that previously contained all n keys + /// results in a domain of size n less than the original domain. + pub proof fn lemma_remove_keys_len(self, keys: ISet) + requires + forall|k: K| #[trigger] keys.contains(k) ==> self.contains_key(k), + keys.finite(), + self.dom().finite(), + ensures + self.remove_keys(keys).dom().len() == self.dom().len() - keys.len(), + decreases keys.len(), + { + broadcast use group_iset_properties; + + if keys.len() > 0 { + let key = keys.choose(); + let val = self[key]; + self.remove(key).lemma_remove_keys_len(keys.remove(key)); + assert(self.remove(key).remove_keys(keys.remove(key)) =~= self.remove_keys(keys)); + } else { + assert(self.remove_keys(keys) =~= self); + } + } + + /// The function `invert` results in an injective map + pub proof fn lemma_invert_is_injective(self) + ensures + self.invert().is_injective(), + { + assert forall|x: V, y: V| + x != y && self.invert().dom().contains(x) && self.invert().dom().contains( + y, + ) implies #[trigger] self.invert()[x] != #[trigger] self.invert()[y] by { + let i = choose|i: K| #[trigger] self.dom().contains(i) && self[i] == x; + assert(self.contains_pair(i, x)); + let j = choose|j: K| self.contains_pair(j, x) && self.invert()[x] == j; + let k = choose|k: K| #[trigger] self.dom().contains(k) && self[k] == y; + assert(self.contains_pair(k, y)); + let l = choose|l: K| self.contains_pair(l, y) && self.invert()[y] == l && l != j; + } + } + + /// Keeps only those key-value pairs whose key satisfies `p`. + /// + /// ## Example + /// ```rust + /// proof fn example() { + /// let m = imap![1 => 10, 2 => 20]; + /// let even = |k: int| k % 2 == 0; + /// assert(m.filter_keys(even) =~= imap![2 => 20]); + /// } + /// ``` + pub open spec fn filter_keys(self, p: spec_fn(K) -> bool) -> Self { + self.restrict(self.dom().filter(p)) + } + + /// Returns the value associated with key `k` in the map if it exists, + /// otherwise returns None. + /// + /// ## Example + /// ```rust + /// proof fn get_test() { + /// let m: IMap = imap![ + /// 1 => true, + /// 2 => false + /// ]; + /// + /// assert(m.get(1) == Some(true)); + /// assert(m.get(3) == None); + /// } + /// ``` + pub open spec fn get(self, k: K) -> Option { + if self.dom().contains(k) { + Some(self[k]) + } else { + None + } + } + + /// A map contains a value satisfying predicate `p` iff some key maps to a value satisfying `p`. + /// + /// ## Example + /// ```rust + /// proof fn get_dom_value_any_test() { + /// let m = imap![1 => 10, 2 => 20, 3 => 30]; + /// let p = |v: int| v > 25; + /// assert(m[3] == 30); + /// assert(m.dom().any(|k| p(m[k]))); + /// } + /// ``` + pub proof fn get_dom_value_any(self, p: spec_fn(V) -> bool) + ensures + self.dom().any(|k: K| p(self[k])) <==> self.values().any(p), + { + broadcast use group_imap_properties; + + if self.dom().any(|k: K| p(self[k])) { + let k = choose|k: K| self.dom().contains(k) && #[trigger] p(self[k]); + assert(self.values().contains(self[k])); + assert(self.values().any(p)); + } + } + + /// Two maps are equal if and only if they are submaps of each other. + /// + /// ## Example + /// ```rust + /// proof fn submap_eq_test() { + /// let m1 = imap![1 => 10, 2 => 20]; + /// let m2 = imap![1 => 10, 2 => 20]; + /// let m3 = imap![1 => 10, 2 => 30]; + /// + /// assert(m1.submap_of(m2) && m2.submap_of(m1)); + /// assert(m1 == m2); + /// } + /// ``` + pub proof fn lemma_submap_eq_iff(self, m: Self) + ensures + (self == m) <==> (self.submap_of(m) && m.submap_of(self)), + { + broadcast use group_imap_properties; + + if self.submap_of(m) && m.submap_of(self) { + assert(self.dom() == m.dom()); + assert(self == m); + } + } + + /// When removing a set of keys from a map after inserting (k,v), + /// the result depends on whether k is in the set: + /// if k is in the set, the insertion is discarded; + /// if not, the insertion happens after removal. + /// + /// ## Example + /// ```rust + /// proof fn map_remove_keys_insert_test() { + /// let m = imap![1 => 10, 2 => 20, 3 => 30]; + /// let to_remove = set![2, 4]; + /// + /// // 5 not in m: insert happens after remove + /// assert(m.insert(5, 15).remove_keys(to_remove) == m.remove_keys(to_remove).insert(5, 15)); + /// + /// // 2 in m: insert is eliminated by remove + /// assert(m.insert(2, 25).remove_keys(to_remove) == m.remove_keys(to_remove)); + /// } + /// ``` + pub broadcast proof fn lemma_imap_remove_keys_insert(self, r: ISet, k: K, v: V) + ensures + #[trigger] self.insert(k, v).remove_keys(r) == if r.contains(k) { + self.remove_keys(r) + } else { + self.remove_keys(r).insert(k, v) + }, + { + broadcast use group_imap_properties; + + let lhs = self.insert(k, v).remove_keys(r); + let rhs = if r.contains(k) { + self.remove_keys(r) + } else { + self.remove_keys(r).insert(k, v) + }; + assert(lhs == rhs); + } + + /// Filtering keys after inserting `(k, v)` leaves the result unchanged when `p(k)` is false, + /// and otherwise adds `(k, v)` to the already-filtered map. + /// + /// ## Example + /// ```rust + /// proof fn example() { + /// let m = imap![1 => 10]; + /// let even = |k: int| k % 2 == 0; + /// + /// assert(m.insert(3, 30).filter_keys(even) =~= m.filter_keys(even)); + /// assert(m.insert(2, 20).filter_keys(even) + /// =~= m.filter_keys(even).insert(2, 20)); + /// } + /// ``` + pub broadcast proof fn lemma_filter_keys_insert(self, p: spec_fn(K) -> bool, k: K, v: V) + ensures + #[trigger] self.insert(k, v).filter_keys(p) == (if p(k) { + self.filter_keys(p).insert(k, v) + } else { + self.filter_keys(p) + }), + { + broadcast use group_imap_properties; + + let lhs = self.insert(k, v).filter_keys(p); + let rhs = if p(k) { + self.filter_keys(p).insert(k, v) + } else { + self.filter_keys(p) + }; + assert(lhs == rhs); + } + + /// Inserting a value in a map means it contains the value that was inserted. + /// + /// ## Example + /// ```rust + /// proof fn example() { + /// let a = imap![1int => 2int]; + /// assert(a.contains_value(2)); + /// } + /// ``` + pub broadcast proof fn lemma_insert_contains_value(self, k: K, v: V) + ensures + #[trigger] self.insert(k, v).contains_value(v), + { + assert(self.insert(k, v).contains_key(k)); + } + + /// Inserting a value in a map where that key is already present ensures + /// The contains value sees (only) the updated value; + /// + /// ## Example + /// ```rust + /// proof fn example() { + /// let a = imap![1int => 2int].insert(1int, 3int); + /// assert(!a.contains_value(2)); + /// assert(a.contains_value(3)); + /// } + /// ``` + pub broadcast proof fn lemma_insert_invariant_contains(self, old_v: V, k: K, v: V) + requires + self.contains_key(k) ==> self[k] != old_v, + old_v != v, + ensures + #[trigger] self.insert(k, v).contains_value(old_v) == self.contains_value(old_v), + { + if self.contains_value(old_v) { + let old_k = choose|key: K| #[trigger] self.dom().contains(key) && self[key] == old_v; + assert(self.insert(k, v).contains_key(old_k)); + } + } + + pub proof fn lemma_injective_values_len(self) + requires + self.dom().finite(), + self.is_injective(), + ensures + self.values().finite(), + self.values().len() == self.dom().len(), + { + let f = |k: K| + if self.contains_key(k) { + self[k] + } else { + ISet::::full().difference(self.values()).choose() + }; + assert(forall|a1: K, a2: K| + self.dom().contains(a1) && self.dom().contains(a2) && #[trigger] f(a1) == #[trigger] f( + a2, + ) ==> a1 == a2); + assert(self.dom().map(f) == self.values()); + super::iset_lib::lemma_map_size(self.dom(), self.values(), f); + } + + pub proof fn lemma_values_len(self) + requires + self.dom().finite(), + ensures + self.values().finite(), + self.values().len() <= self.dom().len(), + { + let f = |k: K| + if self.contains_key(k) { + self[k] + } else { + ISet::::full().difference(self.values()).choose() + }; + assert(self.dom().map(f) == self.values()); + super::iset_lib::lemma_map_size_bound(self.dom(), self.values(), f); + } +} + +impl IMap, V> { + /// Returns a sub-map of all entries whose key begins with `prefix`, + /// re-indexed so that the stored keys have that prefix removed. + /// + /// ## Example + /// ```rust + /// proof fn example() { + /// let m = imap![seq![1, 2] => 10, seq![1, 2, 3] => 20, seq![2] => 30]; + /// let sub = m.prefixed_entries(seq![1, 2]); + /// + /// assert(sub.contains_key(seq![])); // original key [1,2] + /// assert(sub[seq![]] == 10); + /// + /// assert(sub.contains_key(seq![3])); // original key [1,2,3] + /// assert(sub[seq![3]] == 20); + /// + /// assert(!sub.contains_key(seq![2])); // original key [2] was not prefixed + /// } + /// ``` + pub open spec fn prefixed_entries(self, prefix: Seq) -> Self { + IMap::new(|k: Seq| self.contains_key(prefix + k), |k: Seq| self[prefix + k]) + } + + /// For every key `k` kept by `prefixed_entries(prefix)`, + /// the associated value is the one stored at `prefix + k` in the original map. + /// + /// ## Example + /// ```rust + /// proof fn example() { + /// let m = imap![seq![1, 2] => 10, seq![1, 2, 3] => 20]; + /// let p = seq![1, 2]; + /// + /// // key inside the sub-map + /// assert(m.prefixed_entries(p)[seq![]] == m[p + seq![]]); // 10 + /// assert(m.prefixed_entries(p)[seq![3]] == m[p + seq![3]]); // 20 + /// } + /// ``` + pub broadcast proof fn lemma_prefixed_entries_get(self, prefix: Seq, k: Seq) + requires + self.prefixed_entries(prefix).contains_key(k), + ensures + self.prefixed_entries(prefix)[k] == #[trigger] self[prefix + k], + { + broadcast use group_imap_properties; + + } + + /// A key `k` is in `prefixed_entries(prefix)` exactly when the original map + /// contains the key `prefix + k`. + /// + /// ## Example + /// ```rust + /// proof fn example() { + /// let m = imap![seq![1, 2] => 10, seq![1, 2, 3] => 20]; + /// let p = seq![1, 2]; + /// + /// assert(m.prefixed_entries(p).contains_key(seq![]) + /// <==> m.contains_key(p + seq![])); + /// + /// assert(m.prefixed_entries(p).contains_key(seq![3]) + /// <==> m.contains_key(p + seq![3])); + /// + /// assert(!m.prefixed_entries(p).contains_key(seq![2])); + /// } + /// ``` + pub broadcast proof fn lemma_prefixed_entries_contains(self, prefix: Seq, k: Seq) + ensures + #[trigger] self.prefixed_entries(prefix).contains_key(k) <==> self.contains_key( + prefix + k, + ), + { + broadcast use group_imap_properties; + + let lhs = self.prefixed_entries(prefix); + let rhs = self; + } + + /// Inserting `(prefix + k, v)` before taking `prefixed_entries(prefix)` + /// has the same effect as inserting `(k, v)` into the prefixed map. + /// + /// ## Example + /// ```rust + /// proof fn example() { + /// let m = imap![seq![1, 2] => 10]; + /// let p = seq![1, 2]; + /// + /// let left = m.insert(p + seq![3], 20).prefixed_entries(p); + /// let right = m.prefixed_entries(p).insert(seq![3], 20); + /// + /// assert(left == right); + /// } + /// ``` + pub broadcast proof fn lemma_prefixed_entries_insert(self, prefix: Seq, k: Seq, v: V) + ensures + #[trigger] self.insert(prefix + k, v).prefixed_entries(prefix) == self.prefixed_entries( + prefix, + ).insert(k, v), + { + broadcast use group_imap_properties; + broadcast use super::seq::group_seq_axioms; + broadcast use super::seq_lib::group_seq_properties, super::seq_lib::lemma_seq_skip_of_skip; + broadcast use Map::lemma_prefixed_entries_contains, Map::lemma_prefixed_entries_get; + + let lhs = self.insert(prefix + k, v).prefixed_entries(prefix); + let rhs = self.prefixed_entries(prefix).insert(k, v); + assert(lhs =~= rhs) by { + assert(forall|key| key != k ==> prefix.is_prefix_of(#[trigger] (prefix + key))); + } + } + + /// Taking the entries that share `prefix` commutes with `union_prefer_right`: + /// union the two maps first and then strip the prefix, or strip the prefix from + /// each map and then union them—the resulting maps are identical. + /// + /// ## Example + /// ```rust + /// proof fn example() { + /// let a = imap![seq![1, 2] => 10, seq![2, 3] => 20]; + /// let b = imap![seq![1, 2] => 99, seq![2, 4] => 40]; + /// let p = seq![2]; + /// + /// assert( + /// a.union_prefer_right(b).prefixed_entries(p) + /// == a.prefixed_entries(p).union_prefer_right(b.prefixed_entries(p)) + /// ); + /// } + /// ``` + pub broadcast proof fn lemma_prefixed_entries_union(self, m: Self, prefix: Seq) + ensures + #[trigger] self.union_prefer_right(m).prefixed_entries(prefix) == self.prefixed_entries( + prefix, + ).union_prefer_right(m.prefixed_entries(prefix)), + { + broadcast use group_imap_properties; + broadcast use + IMap::lemma_prefixed_entries_contains, + IMap::lemma_prefixed_entries_get, + IMap::lemma_prefixed_entries_insert, + ; + + let lhs = self.union_prefer_right(m).prefixed_entries(prefix); + let rhs = self.prefixed_entries(prefix).union_prefer_right(m.prefixed_entries(prefix)); + assert(lhs == rhs); + } +} + +impl IMap { + /// Returns `true` if a map is monotonic -- that is, if the mapping between ordered sets + /// preserves the regular `<=` ordering on integers. + pub open spec fn is_monotonic(self) -> bool { + forall|x: int, y: int| + self.dom().contains(x) && self.dom().contains(y) && x <= y ==> #[trigger] self[x] + <= #[trigger] self[y] + } + + /// Returns `true` if and only if a map is monotonic, only considering keys greater than + /// or equal to start + pub open spec fn is_monotonic_from(self, start: int) -> bool { + forall|x: int, y: int| + self.dom().contains(x) && self.dom().contains(y) && start <= x <= y + ==> #[trigger] self[x] <= #[trigger] self[y] + } +} + +// Proven lemmas +pub broadcast proof fn lemma_union_insert_left(m1: IMap, m2: IMap, k: K, v: V) + requires + !m2.contains_key(k), + ensures + #[trigger] m1.insert(k, v).union_prefer_right(m2) == m1.union_prefer_right(m2).insert(k, v), +{ + assert(m1.insert(k, v).union_prefer_right(m2) =~= m1.union_prefer_right(m2).insert(k, v)); +} + +pub broadcast proof fn lemma_union_insert_right(m1: IMap, m2: IMap, k: K, v: V) + ensures + #[trigger] m1.union_prefer_right(m2.insert(k, v)) == m1.union_prefer_right(m2).insert(k, v), +{ + assert(m1.union_prefer_right(m2.insert(k, v)) =~= m1.union_prefer_right(m2).insert(k, v)); +} + +pub broadcast proof fn lemma_union_remove_left(m1: IMap, m2: IMap, k: K) + requires + m1.contains_key(k), + !m2.contains_key(k), + ensures + #[trigger] m1.union_prefer_right(m2).remove(k) == m1.remove(k).union_prefer_right(m2), +{ + assert(m1.remove(k).union_prefer_right(m2) =~= m1.union_prefer_right(m2).remove(k)); +} + +pub broadcast proof fn lemma_union_remove_right(m1: IMap, m2: IMap, k: K) + requires + !m1.contains_key(k), + m2.contains_key(k), + ensures + #[trigger] m1.union_prefer_right(m2).remove(k) == m1.union_prefer_right(m2.remove(k)), +{ + assert(m1.union_prefer_right(m2.remove(k)) =~= m1.union_prefer_right(m2).remove(k)); +} + +pub broadcast proof fn lemma_union_dom(m1: IMap, m2: IMap) + ensures + #[trigger] m1.union_prefer_right(m2).dom() == m1.dom().union(m2.dom()), +{ + assert(m1.dom().union(m2.dom()) =~= m1.union_prefer_right(m2).dom()); +} + +/// The size of the union of two disjoint maps is equal to the sum of the sizes of the individual maps +pub broadcast proof fn lemma_disjoint_union_size(m1: IMap, m2: IMap) + requires + m1.dom().disjoint(m2.dom()), + m1.dom().finite(), + m2.dom().finite(), + ensures + #[trigger] m1.union_prefer_right(m2).dom().len() == m1.dom().len() + m2.dom().len(), +{ + let u = m1.union_prefer_right(m2); + assert(u.dom() =~= m1.dom() + m2.dom()); //proves u.dom() is finite + assert(u.remove_keys(m1.dom()).dom() =~= m2.dom()); + assert(u.remove_keys(m1.dom()).dom().len() == u.dom().len() - m1.dom().len()) by { + u.lemma_remove_keys_len(m1.dom()); + } +} + +pub broadcast group group_imap_union { + lemma_union_dom, + lemma_union_remove_left, + lemma_union_remove_right, + lemma_union_insert_left, + lemma_union_insert_right, + lemma_disjoint_union_size, +} + +/// submap_of (<=) is transitive. +pub broadcast proof fn lemma_submap_of_trans(m1: IMap, m2: IMap, m3: IMap) + requires + #[trigger] m1.submap_of(m2), + #[trigger] m2.submap_of(m3), + ensures + m1.submap_of(m3), +{ + assert forall|k| m1.dom().contains(k) implies #[trigger] m3.dom().contains(k) && m1[k] + == m3[k] by { + assert(m2.dom().contains(k)); + } +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// The domain of a map constructed with `IMap::new(fk, fv)` is equivalent to the set constructed with `ISet::new(fk)`. +pub broadcast proof fn lemma_imap_new_domain(fk: spec_fn(K) -> bool, fv: spec_fn(K) -> V) + ensures + #[trigger] IMap::::new(fk, fv).dom() == ISet::::new(|k: K| fk(k)), +{ + assert(ISet::new(fk) =~= ISet::::new(|k: K| fk(k))); +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// The set of values of a map constructed with `IMap::new(fk, fv)` is equivalent to +/// the set constructed with `ISet::new(|v: V| (exists |k: K| fk(k) && fv(k) == v)`. In other words, +/// the set of all values fv(k) where fk(k) is true. +pub broadcast proof fn lemma_imap_new_values(fk: spec_fn(K) -> bool, fv: spec_fn(K) -> V) + ensures + #[trigger] IMap::::new(fk, fv).values() == ISet::::new( + |v: V| (exists|k: K| #[trigger] fk(k) && #[trigger] fv(k) == v), + ), +{ + let keys = ISet::::new(fk); + let values = IMap::::new(fk, fv).values(); + let map = IMap::::new(fk, fv); + assert(map.dom() =~= keys); + assert(forall|k: K| #[trigger] fk(k) ==> keys.contains(k)); + assert(values =~= ISet::::new( + |v: V| (exists|k: K| #[trigger] fk(k) && #[trigger] fv(k) == v), + )); +} + +pub broadcast group group_imap_properties { + lemma_imap_new_domain, + lemma_imap_new_values, +} + +pub broadcast group group_imap_extra { + IMap::lemma_imap_remove_keys_insert, + IMap::lemma_filter_keys_insert, + IMap::lemma_insert_contains_value, + IMap::lemma_insert_invariant_contains, + IMap::lemma_prefixed_entries_get, + IMap::lemma_prefixed_entries_contains, + IMap::lemma_prefixed_entries_insert, + IMap::lemma_prefixed_entries_union, +} + +pub proof fn lemma_values_finite(m: IMap) + requires + m.dom().finite(), + ensures + m.values().finite(), + decreases m.len(), +{ + if m.len() > 0 { + let k = m.dom().choose(); + let v = m[k]; + let m1 = m.remove(k); + assert(m.contains_key(k)); + assert(m.contains_value(v)); + let mv = m.values(); + let m1v = m1.values(); + assert_isets_equal!(mv == m1v.insert(v), v0 => { + if m.contains_value(v0) { + if v0 != v { + let k0 = choose|k0| #![auto] m.contains_key(k0) && m[k0] == v0; + assert(k0 != k); + assert(m1.contains_key(k0)); + assert(mv.contains(v0) ==> m1v.insert(v).contains(v0)); + assert(mv.contains(v0) <== m1v.insert(v).contains(v0)); + } + } + }); + assert(m1.len() < m.len()); + lemma_values_finite(m1); + lemma_iset_insert_finite(m1.values(), v); + } else { + assert(m.values() =~= ISet::::empty()); + } +} + +} // verus! diff --git a/source/vstd/iset.rs b/source/vstd/iset.rs new file mode 100644 index 0000000000..49b13dc5a4 --- /dev/null +++ b/source/vstd/iset.rs @@ -0,0 +1,1090 @@ +#[allow(unused_imports)] +use super::map::*; +#[allow(unused_imports)] +use super::pervasive::*; +#[allow(unused_imports)] +use super::prelude::*; + +verus! { + +/// `ISet` is a set type for specifications. +/// +/// An object `set: ISet` is a subset of the set of all values `a: A`. +/// Equivalently, it can be thought of as a boolean predicate on `A`. +/// +/// In general, a set might be infinite. +/// To work specifically with finite sets, see the [`self.finite()`](ISet::finite) predicate. +/// +/// ISets can be constructed in a few different ways: +/// * [`ISet::empty`] gives an empty set +/// * [`ISet::full`] gives the set of all elements in `A` +/// * [`ISet::new`] constructs a set from a boolean predicate +/// * The [`iset!`] macro, to construct small sets of a fixed size +/// * By manipulating an existing sequence with [`ISet::union`], [`ISet::intersect`], +/// [`ISet::difference`], [`ISet::complement`], [`ISet::filter`], [`ISet::insert`], +/// or [`ISet::remove`]. +/// +/// To prove that two sequences are equal, it is usually easiest to use the extensionality +/// operator `=~=`. +#[verifier::ext_equal] +#[verifier::reject_recursive_types(A)] +pub struct ISet { + set: spec_fn(A) -> bool, +} + +impl ISet { + /// The "empty" set. + /// + /// Usage Example:
+ /// ```rust + /// let empty_set = ISet::
::empty(); + /// + /// assert(empty_set.is_empty()); + /// assert(empty_set.complement() =~= ISet::::full()); + /// assert(ISet::::empty().finite()); + /// assert(ISet::::empty().len() == 0); + /// assert(forall |x: A| !ISet::::empty().contains(x)); + /// ``` + /// Axioms around the empty set are:
+ /// * [`lemma_iset_empty_finite`] + /// * [`lemma_iset_empty_len`]
+ /// * [`lemma_iset_empty`] + #[rustc_diagnostic_item = "verus::vstd::iset::ISet::empty"] + pub closed spec fn empty() -> ISet
{ + ISet { set: |a| false } + } + + /// ISet whose membership is determined by the given boolean predicate. + /// + /// Usage Examples: + /// ```rust + /// let set_a = ISet::new(|x : nat| x < 42); + /// let set_b = ISet::::new(|x| some_predicate(x)); + /// assert(forall|x| some_predicate(x) <==> set_b.contains(x)); + /// ``` + pub closed spec fn new(f: spec_fn(A) -> bool) -> ISet { + ISet { set: f } + } + + /// The "full" set, i.e., set containing every element of type `A`. + #[rustc_diagnostic_item = "verus::vstd::iset::ISet::full"] + pub open spec fn full() -> ISet { + ISet::empty().complement() + } + + /// Predicate indicating if the set contains the given element. + #[rustc_diagnostic_item = "verus::vstd::iset::ISet::contains"] + pub closed spec fn contains(self, a: A) -> bool { + (self.set)(a) + } + + /// Predicate indicating if the set contains the given element: supports `self has a` syntax. + #[verifier::inline] + pub open spec fn spec_has(self, a: A) -> bool { + self.contains(a) + } + + /// Returns `true` if the first argument is a subset of the second. + #[rustc_diagnostic_item = "verus::vstd::iset::ISet::subset_of"] + pub open spec fn subset_of(self, s2: ISet) -> bool { + forall|a: A| self.contains(a) ==> s2.contains(a) + } + + #[verifier::inline] + pub open spec fn spec_le(self, s2: ISet) -> bool { + self.subset_of(s2) + } + + /// Returns a new set with the given element inserted. + /// If that element is already in the set, then an identical set is returned. + #[rustc_diagnostic_item = "verus::vstd::iset::ISet::insert"] + pub closed spec fn insert(self, a: A) -> ISet { + ISet { + set: |a2| + if a2 == a { + true + } else { + (self.set)(a2) + }, + } + } + + /// Returns a new set with the given element removed. + /// If that element is already absent from the set, then an identical set is returned. + #[rustc_diagnostic_item = "verus::vstd::iset::ISet::remove"] + pub closed spec fn remove(self, a: A) -> ISet { + ISet { + set: |a2| + if a2 == a { + false + } else { + (self.set)(a2) + }, + } + } + + /// Union of two sets. + pub closed spec fn union(self, s2: ISet) -> ISet { + ISet { set: |a| (self.set)(a) || (s2.set)(a) } + } + + /// `+` operator, synonymous with `union` + #[verifier::inline] + pub open spec fn spec_add(self, s2: ISet) -> ISet { + self.union(s2) + } + + /// Intersection of two sets. + pub closed spec fn intersect(self, s2: ISet) -> ISet { + ISet { set: |a| (self.set)(a) && (s2.set)(a) } + } + + /// `*` operator, synonymous with `intersect` + #[verifier::inline] + pub open spec fn spec_mul(self, s2: ISet) -> ISet { + self.intersect(s2) + } + + /// ISet difference, i.e., the set of all elements in the first one but not in the second. + pub closed spec fn difference(self, s2: ISet) -> ISet { + ISet { set: |a| (self.set)(a) && !(s2.set)(a) } + } + + /// `-` operator, synonymous with `difference` + #[verifier::inline] + pub open spec fn spec_sub(self, s2: ISet) -> ISet { + self.difference(s2) + } + + /// ISet complement (within the space of all possible elements in `A`). + pub closed spec fn complement(self) -> ISet { + ISet { set: |a| !(self.set)(a) } + } + + /// ISet of all elements in the given set which satisfy the predicate `f`. + pub open spec fn filter(self, f: spec_fn(A) -> bool) -> ISet { + self.intersect(Self::new(f)) + } + + /// Returns `true` if the set is finite. + pub closed spec fn finite(self) -> bool { + exists|f: spec_fn(A) -> nat, ub: nat| + { + &&& #[trigger] trigger_finite(f, ub) + &&& surj_on(f, self) + &&& forall|a| self.contains(a) ==> f(a) < ub + } + } + + pub open spec fn to_set(self) -> Option> + recommends + self.finite(), + { + Set::::new_from_iset(self) + } + + /// Cardinality of the set. (Only meaningful if a set is finite.) + pub closed spec fn len(self) -> nat { + self.fold(0, |acc: nat, a| acc + 1) + } + + /// Chooses an arbitrary element of the set. + /// + /// This is often useful for proofs by induction. + /// + /// (Note that, although the result is arbitrary, it is still a _deterministic_ function + /// like any other `spec` function.) + pub open spec fn choose(self) -> A { + choose|a: A| self.contains(a) + } + + /// Creates a [`Map`] whose domain is the given set. + /// The values of the map are given by `f`, a function of the keys. + pub uninterp spec fn mk_map(self, f: spec_fn(A) -> V) -> IMap; + + /// Returns `true` if the sets are disjoint, i.e., if their interesection is + /// the empty set. + pub open spec fn disjoint(self, s2: Self) -> bool { + forall|a: A| self.contains(a) ==> !s2.contains(a) + } + + /// Returns `true` if this set is congruent to (contains the same elements as) + /// a given finite Set. + pub open spec fn congruent(self, s2: Set) -> bool { + forall|a: A| #![all_triggers] self.contains(a) <==> s2.contains(a) + } +} + +// Closures make triggering finicky but using this to trigger explicitly works well. +spec fn trigger_finite(f: spec_fn(A) -> nat, ub: nat) -> bool { + true +} + +spec fn surj_on(f: spec_fn(A) -> B, s: ISet) -> bool { + forall|a1, a2| #![all_triggers] s.contains(a1) && s.contains(a2) && a1 != a2 ==> f(a1) != f(a2) +} + +pub mod fold { + //! This module defines a fold function for finite sets and proves a number of associated + //! lemmas. + //! + //! The module was ported (with some modifications) from Isabelle/HOL's finite set theory in: + //! `HOL/Finite_ISet.thy` + //! That file contains the following author list: + //! + //! + //! (* Title: HOL/Finite_ISet.thy + //! Author: Tobias Nipkow + //! Author: Lawrence C Paulson + //! Author: Markus Wenzel + //! Author: Jeremy Avigad + //! Author: Andrei Popescu + //! *) + //! + //! + //! The file is distributed under a 3-clause BSD license as indicated in the file `COPYRIGHT` + //! in Isabelle's root directory, which also carries the following copyright notice: + //! + //! Copyright (c) 1986-2024, + //! University of Cambridge, + //! Technische Universitaet Muenchen, + //! and contributors. + use super::*; + + broadcast group group_iset_lemmas_early { + lemma_iset_empty, + lemma_iset_new, + lemma_iset_insert_same, + lemma_iset_insert_different, + lemma_iset_remove_same, + lemma_iset_remove_insert, + lemma_iset_remove_different, + lemma_iset_union, + lemma_iset_intersect, + lemma_iset_difference, + lemma_iset_complement, + lemma_iset_ext_equal, + lemma_iset_ext_equal_deep, + lemma_iset_empty_finite, + lemma_iset_insert_finite, + lemma_iset_remove_finite, + } + + pub open spec fn is_fun_commutative(f: spec_fn(B, A) -> B) -> bool { + forall|a1, a2, b| #[trigger] f(f(b, a2), a1) == f(f(b, a1), a2) + } + + // This predicate is intended to be used like an inductive predicate, with the corresponding + // introduction, elimination and induction rules proved below. + #[verifier(opaque)] + spec fn fold_graph(z: B, f: spec_fn(B, A) -> B, s: ISet, y: B, d: nat) -> bool + decreases d, + { + if s === ISet::empty() { + &&& z == y + &&& d == 0 + } else { + exists|yr, a| + { + &&& #[trigger] trigger_fold_graph(yr, a) + &&& d > 0 + &&& s.remove(a).finite() + &&& s.contains(a) + &&& fold_graph(z, f, s.remove(a), yr, sub(d, 1)) + &&& y == f(yr, a) + } + } + } + + spec fn trigger_fold_graph(yr: B, a: A) -> bool { + true + } + + // Introduction rules + proof fn lemma_fold_graph_empty_intro(z: B, f: spec_fn(B, A) -> B) + ensures + fold_graph(z, f, ISet::empty(), z, 0), + { + reveal(fold_graph); + } + + proof fn lemma_fold_graph_insert_intro( + z: B, + f: spec_fn(B, A) -> B, + s: ISet, + y: B, + d: nat, + a: A, + ) + requires + fold_graph(z, f, s, y, d), + !s.contains(a), + ensures + fold_graph(z, f, s.insert(a), f(y, a), d + 1), + { + broadcast use group_iset_lemmas_early; + + reveal(fold_graph); + let _ = trigger_fold_graph(y, a); + assert(s == s.insert(a).remove(a)); + } + + // Elimination rules + proof fn lemma_fold_graph_empty_elim(z: B, f: spec_fn(B, A) -> B, y: B, d: nat) + requires + fold_graph(z, f, ISet::empty(), y, d), + ensures + z == y, + d == 0, + { + reveal(fold_graph); + } + + proof fn lemma_fold_graph_insert_elim( + z: B, + f: spec_fn(B, A) -> B, + s: ISet, + y: B, + d: nat, + a: A, + ) + requires + is_fun_commutative(f), + fold_graph(z, f, s.insert(a), y, d), + !s.contains(a), + ensures + d > 0, + exists|yp| y == f(yp, a) && #[trigger] fold_graph(z, f, s, yp, sub(d, 1)), + { + reveal(fold_graph); + lemma_fold_graph_insert_elim_aux(z, f, s.insert(a), y, d, a); + assert(s.insert(a).remove(a) =~= s); + let yp = choose|yp| y == f(yp, a) && #[trigger] fold_graph(z, f, s, yp, sub(d, 1)); + } + + proof fn lemma_fold_graph_insert_elim_aux( + z: B, + f: spec_fn(B, A) -> B, + s: ISet, + y: B, + d: nat, + a: A, + ) + requires + is_fun_commutative(f), + fold_graph(z, f, s, y, d), + s.contains(a), + ensures + exists|yp| y == f(yp, a) && #[trigger] fold_graph(z, f, s.remove(a), yp, sub(d, 1)), + decreases d, + { + broadcast use group_iset_lemmas_early; + + reveal(fold_graph); + let (yr, aa): (B, A) = choose|yr, aa| + #![all_triggers] + { + &&& trigger_fold_graph(yr, a) + &&& d > 0 + &&& s.remove(aa).finite() + &&& s.contains(aa) + &&& fold_graph(z, f, s.remove(aa), yr, sub(d, 1)) + &&& y == f(yr, aa) + }; + assert(trigger_fold_graph(yr, a)); + if s.remove(aa) === ISet::empty() { + } else { + if a == aa { + } else { + lemma_fold_graph_insert_elim_aux(z, f, s.remove(aa), yr, sub(d, 1), a); + let yrp = choose|yrp| + yr == f(yrp, a) && #[trigger] fold_graph( + z, + f, + s.remove(aa).remove(a), + yrp, + sub(d, 2), + ); + assert(fold_graph(z, f, s.remove(aa).insert(aa).remove(a), f(yrp, aa), sub(d, 1))) + by { + assert(s.remove(aa).remove(a) == s.remove(aa).insert(aa).remove(a).remove(aa)); + assert(trigger_fold_graph(yrp, aa)); + }; + } + } + } + + // Induction rule + proof fn lemma_fold_graph_induct( + z: B, + f: spec_fn(B, A) -> B, + s: ISet, + y: B, + d: nat, + pred: spec_fn(ISet, B, nat) -> bool, + ) + requires + is_fun_commutative(f), + fold_graph(z, f, s, y, d), + pred(ISet::empty(), z, 0), + forall|a, s, y, d| + pred(s, y, d) && !s.contains(a) && #[trigger] fold_graph(z, f, s, y, d) ==> pred( + #[trigger] s.insert(a), + f(y, a), + d + 1, + ), + ensures + pred(s, y, d), + decreases d, + { + broadcast use group_iset_lemmas_early; + + reveal(fold_graph); + if s === ISet::empty() { + lemma_fold_graph_empty_elim(z, f, y, d); + } else { + let a = s.choose(); + lemma_fold_graph_insert_elim(z, f, s.remove(a), y, d, a); + let yp = choose|yp| + y == f(yp, a) && #[trigger] fold_graph(z, f, s.remove(a), yp, sub(d, 1)); + lemma_fold_graph_induct(z, f, s.remove(a), yp, sub(d, 1), pred); + } + } + + impl ISet { + /// Folds the set, applying `f` to perform the fold. The next element for the fold is chosen by + /// the choose operator. + /// + /// Given a set `s = {x0, x1, x2, ..., xn}`, applying this function `s.fold(init, f)` + /// returns `f(...f(f(init, x0), x1), ..., xn)`. + pub closed spec fn fold(self, z: B, f: spec_fn(B, A) -> B) -> B + recommends + self.finite(), + is_fun_commutative(f), + { + let (y, d): (B, nat) = choose|y, d| fold_graph(z, f, self, y, d); + y + } + } + + proof fn lemma_fold_graph_finite(z: B, f: spec_fn(B, A) -> B, s: ISet, y: B, d: nat) + requires + is_fun_commutative(f), + fold_graph(z, f, s, y, d), + ensures + s.finite(), + { + broadcast use group_iset_lemmas_early; + + let pred = |s: ISet, y, d| s.finite(); + lemma_fold_graph_induct(z, f, s, y, d, pred); + } + + proof fn lemma_fold_graph_deterministic( + z: B, + f: spec_fn(B, A) -> B, + s: ISet, + y1: B, + y2: B, + d1: nat, + d2: nat, + ) + requires + is_fun_commutative(f), + fold_graph(z, f, s, y1, d1), + fold_graph(z, f, s, y2, d2), + ensures + y1 == y2, + d1 == d2, + { + let pred = |s: ISet, y1: B, d1: nat| + forall|y2, d2| fold_graph(z, f, s, y2, d2) ==> y1 == y2 && d2 == d1; + // Base case + assert(pred(ISet::empty(), z, 0)) by { + assert forall|y2, d2| fold_graph(z, f, ISet::empty(), y2, d2) implies z == y2 && d2 + == 0 by { + lemma_fold_graph_empty_elim(z, f, y2, d2); + }; + }; + // Step case + assert forall|a, s, y1, d1| + pred(s, y1, d1) && !s.contains(a) && #[trigger] fold_graph( + z, + f, + s, + y1, + d1, + ) implies pred(#[trigger] s.insert(a), f(y1, a), d1 + 1) by { + assert forall|y2, d2| fold_graph(z, f, s.insert(a), y2, d2) implies f(y1, a) == y2 && d2 + == d1 + 1 by { + lemma_fold_graph_insert_elim(z, f, s, y2, d2, a); + }; + }; + lemma_fold_graph_induct(z, f, s, y2, d2, pred); + } + + proof fn lemma_fold_is_fold_graph(z: B, f: spec_fn(B, A) -> B, s: ISet, y: B, d: nat) + requires + is_fun_commutative(f), + fold_graph(z, f, s, y, d), + ensures + s.fold(z, f) == y, + { + lemma_fold_graph_finite(z, f, s, y, d); + if s.fold(z, f) != y { + let (y2, d2) = choose|y2, d2| fold_graph(z, f, s, y2, d2) && y2 != y; + lemma_fold_graph_deterministic(z, f, s, y2, y, d2, d); + assert(false); + } + } + + // At this point set cardinality is not yet defined, so we can't easily give a decreasing + // measure to prove the subsequent lemma `lemma_fold_graph_exists`. Instead, we first prove + // this lemma, for which we use the upper bound of a finiteness witness as the decreasing + // measure. + pub proof fn lemma_finite_set_induct(s: ISet, pred: spec_fn(ISet) -> bool) + requires + s.finite(), + pred(ISet::empty()), + forall|s, a| pred(s) && s.finite() && !s.contains(a) ==> #[trigger] pred(s.insert(a)), + ensures + pred(s), + { + let (f, ub) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] + trigger_finite(f, ub) && surj_on(f, s) && (forall|a| s.contains(a) ==> f(a) < ub); + lemma_finite_set_induct_aux(s, f, ub, pred); + } + + proof fn lemma_finite_set_induct_aux( + s: ISet, + f: spec_fn(A) -> nat, + ub: nat, + pred: spec_fn(ISet) -> bool, + ) + requires + surj_on(f, s), + s.finite(), + forall|a| s.contains(a) ==> f(a) < ub, + pred(ISet::empty()), + forall|s, a| pred(s) && s.finite() && !s.contains(a) ==> #[trigger] pred(s.insert(a)), + ensures + pred(s), + decreases ub, + { + broadcast use group_iset_lemmas_early; + + if s =~= ISet::empty() { + } else { + let a = s.choose(); + // If `f` maps something to `ub - 1`, remap it to `f(a)` so we can decrease ub + let fp = |aa| + if f(aa) == ub - 1 { + f(a) + } else { + f(aa) + }; + lemma_finite_set_induct_aux(s.remove(a), fp, sub(ub, 1), pred); + } + } + + proof fn lemma_fold_graph_exists(z: B, f: spec_fn(B, A) -> B, s: ISet) + requires + s.finite(), + is_fun_commutative(f), + ensures + exists|y, d| fold_graph(z, f, s, y, d), + { + let pred = |s| exists|y, d| fold_graph(z, f, s, y, d); + // Base case + assert(fold_graph(z, f, ISet::empty(), z, 0)) by { + lemma_fold_graph_empty_intro(z, f); + }; + // Step case + assert forall|s, a| pred(s) && s.finite() && !s.contains(a) implies #[trigger] pred( + s.insert(a), + ) by { + let (y, d): (B, nat) = choose|y, d| fold_graph(z, f, s, y, d); + lemma_fold_graph_insert_intro(z, f, s, y, d, a); + }; + lemma_finite_set_induct(s, pred); + } + + pub broadcast proof fn lemma_fold_insert(s: ISet, z: B, f: spec_fn(B, A) -> B, a: A) + requires + s.finite(), + !s.contains(a), + is_fun_commutative(f), + ensures + #[trigger] s.insert(a).fold(z, f) == f(s.fold(z, f), a), + { + lemma_fold_graph_exists(z, f, s); + let (y, d): (B, nat) = choose|y, d| fold_graph(z, f, s, y, d); + lemma_fold_graph_insert_intro(z, f, s, s.fold(z, f), d, a); + lemma_fold_is_fold_graph(z, f, s.insert(a), f(s.fold(z, f), a), d + 1); + } + + pub broadcast proof fn lemma_fold_empty(z: B, f: spec_fn(B, A) -> B) + ensures + #[trigger] ISet::empty().fold(z, f) == z, + { + let (y, d): (B, nat) = choose|y, d| fold_graph(z, f, ISet::empty(), y, d); + lemma_fold_graph_empty_intro(z, f); + lemma_fold_graph_empty_elim(z, f, y, d); + } + +} + +// Axioms +/// The empty set contains no elements +pub broadcast proof fn lemma_iset_empty(a: A) + ensures + !(#[trigger] ISet::empty().contains(a)), +{ +} + +/// A call to `ISet::new` with the predicate `f` contains `a` if and only if `f(a)` is true. +pub broadcast proof fn lemma_iset_new(f: spec_fn(A) -> bool, a: A) + ensures + #[trigger] ISet::new(f).contains(a) == f(a), +{ +} + +/// The result of inserting element `a` into set `s` must contains `a`. +pub broadcast proof fn lemma_iset_insert_same(s: ISet, a: A) + ensures + #[trigger] s.insert(a).contains(a), +{ +} + +/// If `a1` does not equal `a2`, then the result of inserting element `a2` into set `s` +/// must contain `a1` if and only if the set contained `a1` before the insertion of `a2`. +pub broadcast proof fn lemma_iset_insert_different(s: ISet, a1: A, a2: A) + requires + a1 != a2, + ensures + #[trigger] s.insert(a2).contains(a1) == s.contains(a1), +{ +} + +/// The result of removing element `a` from set `s` must not contain `a`. +pub broadcast proof fn lemma_iset_remove_same(s: ISet, a: A) + ensures + !(#[trigger] s.remove(a).contains(a)), +{ +} + +/// Removing an element `a` from a set `s` and then inserting `a` back into the set` +/// is equivalent to the original set `s`. +pub broadcast proof fn lemma_iset_remove_insert(s: ISet, a: A) + requires + s.contains(a), + ensures + (#[trigger] s.remove(a)).insert(a) == s, +{ + assert forall|aa| #![all_triggers] s.remove(a).insert(a).contains(aa) implies s.contains( + aa, + ) by { + if a == aa { + } else { + lemma_iset_remove_different(s, aa, a); + lemma_iset_insert_different(s.remove(a), aa, a); + } + }; + assert forall|aa| #![all_triggers] s.contains(aa) implies s.remove(a).insert(a).contains( + aa, + ) by { + if a == aa { + lemma_iset_insert_same(s.remove(a), a); + } else { + lemma_iset_remove_different(s, aa, a); + lemma_iset_insert_different(s.remove(a), aa, a); + } + }; + lemma_iset_ext_equal(s.remove(a).insert(a), s); +} + +/// If `a1` does not equal `a2`, then the result of removing element `a2` from set `s` +/// must contain `a1` if and only if the set contained `a1` before the removal of `a2`. +pub broadcast proof fn lemma_iset_remove_different(s: ISet, a1: A, a2: A) + requires + a1 != a2, + ensures + #[trigger] s.remove(a2).contains(a1) == s.contains(a1), +{ +} + +/// The union of sets `s1` and `s2` contains element `a` if and only if +/// `s1` contains `a` and/or `s2` contains `a`. +pub broadcast proof fn lemma_iset_union(s1: ISet, s2: ISet, a: A) + ensures + #[trigger] s1.union(s2).contains(a) == (s1.contains(a) || s2.contains(a)), +{ +} + +/// The intersection of sets `s1` and `s2` contains element `a` if and only if +/// both `s1` and `s2` contain `a`. +pub broadcast proof fn lemma_iset_intersect(s1: ISet, s2: ISet, a: A) + ensures + #[trigger] s1.intersect(s2).contains(a) == (s1.contains(a) && s2.contains(a)), +{ +} + +/// The set difference between `s1` and `s2` contains element `a` if and only if +/// `s1` contains `a` and `s2` does not contain `a`. +pub broadcast proof fn lemma_iset_difference(s1: ISet, s2: ISet, a: A) + ensures + #[trigger] s1.difference(s2).contains(a) == (s1.contains(a) && !s2.contains(a)), +{ +} + +/// The complement of set `s` contains element `a` if and only if `s` does not contain `a`. +pub broadcast proof fn lemma_iset_complement(s: ISet, a: A) + ensures + #[trigger] s.complement().contains(a) == !s.contains(a), +{ +} + +/// ISets `s1` and `s2` are equal if and only if they contain all of the same elements. +pub broadcast proof fn lemma_iset_ext_equal(s1: ISet, s2: ISet) + ensures + #[trigger] (s1 =~= s2) <==> (forall|a: A| s1.contains(a) == s2.contains(a)), +{ + if s1 =~= s2 { + assert(forall|a: A| s1.contains(a) == s2.contains(a)); + } + if forall|a: A| s1.contains(a) == s2.contains(a) { + if !(forall|a: A| #[trigger] (s1.set)(a) <==> (s2.set)(a)) { + assert(exists|a: A| #[trigger] (s1.set)(a) != (s2.set)(a)); + let a = choose|a: A| #[trigger] (s1.set)(a) != (s2.set)(a); + assert(s1.contains(a)); + assert(false); + } + assert(s1 =~= s2); + } +} + +pub broadcast proof fn lemma_iset_ext_equal_eq(s1: ISet, s2: ISet) + ensures + #[trigger] (s1 =~= s2) ==> s1 == s2, +{ + if s1 =~= s2 { + assert(s1 == s2); + } +} + +pub broadcast proof fn lemma_iset_ext_equal_deep(s1: ISet, s2: ISet) + ensures + #[trigger] (s1 =~~= s2) <==> s1 =~= s2, +{ +} + +pub broadcast axiom fn lemma_iset_mk_map_domain(s: ISet, f: spec_fn(K) -> V) + ensures + #[trigger] s.mk_map(f).dom() == s, +; + +pub broadcast axiom fn lemma_iset_mk_map_index(s: ISet, f: spec_fn(K) -> V, key: K) + requires + s.contains(key), + ensures + #[trigger] s.mk_map(f)[key] == f(key), +; + +// Trusted axioms about finite +/// The empty set is finite. +pub broadcast proof fn lemma_iset_empty_finite() + ensures + #[trigger] ISet::::empty().finite(), +{ + let f = |a: A| 0; + let ub = 0; + let _ = trigger_finite(f, ub); +} + +/// The result of inserting an element `a` into a finite set `s` is also finite. +pub broadcast proof fn lemma_iset_insert_finite(s: ISet, a: A) + requires + s.finite(), + ensures + #[trigger] s.insert(a).finite(), +{ + let (f, ub) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] + trigger_finite(f, ub) && surj_on(f, s) && (forall|a| s.contains(a) ==> f(a) < ub); + let f2 = |a2: A| + if a2 == a { + ub + } else { + f(a2) + }; + let ub2 = ub + 1; + let _ = trigger_finite(f2, ub2); + assert forall|a1, a2| + #![all_triggers] + s.insert(a).contains(a1) && s.insert(a).contains(a2) && a1 != a2 implies f2(a1) != f2( + a2, + ) by { + if a != a1 { + assert(s.contains(a1)); + } + if a != a2 { + assert(s.contains(a2)); + } + }; + assert forall|a2| s.insert(a).contains(a2) implies #[trigger] f2(a2) < ub2 by { + if a == a2 { + } else { + assert(s.contains(a2)); + } + }; +} + +/// The result of removing an element `a` from a finite set `s` is also finite. +pub broadcast proof fn lemma_iset_remove_finite(s: ISet, a: A) + requires + s.finite(), + ensures + #[trigger] s.remove(a).finite(), +{ + let (f, ub) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] + trigger_finite(f, ub) && surj_on(f, s) && (forall|a| s.contains(a) ==> f(a) < ub); + assert forall|a1, a2| + #![all_triggers] + s.remove(a).contains(a1) && s.remove(a).contains(a2) && a1 != a2 implies f(a1) != f(a2) by { + if a != a1 { + assert(s.contains(a1)); + } + if a != a2 { + assert(s.contains(a2)); + } + }; + assert(surj_on(f, s.remove(a))); + assert forall|a2| s.remove(a).contains(a2) implies #[trigger] f(a2) < ub by { + if a == a2 { + } else { + assert(s.contains(a2)); + } + }; +} + +/// The union of two finite sets is finite. +pub broadcast proof fn lemma_iset_union_finite(s1: ISet, s2: ISet) + requires + s1.finite(), + s2.finite(), + ensures + #[trigger] s1.union(s2).finite(), +{ + let (f1, ub1) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] + trigger_finite(f, ub) && surj_on(f, s1) && (forall|a| s1.contains(a) ==> f(a) < ub); + let (f2, ub2) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] + trigger_finite(f, ub) && surj_on(f, s2) && (forall|a| s2.contains(a) ==> f(a) < ub); + let f3 = |a| + if s1.contains(a) { + f1(a) + } else { + ub1 + f2(a) + }; + let ub3 = ub1 + ub2; + assert(trigger_finite(f3, ub3)); + assert(forall|a| + #![all_triggers] + s1.union(s2).contains(a) ==> s1.contains(a) || s2.contains(a)); +} + +/// The intersection of two finite sets is finite. +pub broadcast proof fn lemma_iset_intersect_finite(s1: ISet, s2: ISet) + requires + s1.finite() || s2.finite(), + ensures + #[trigger] s1.intersect(s2).finite(), +{ + assert(forall|a| + #![all_triggers] + s1.intersect(s2).contains(a) ==> s1.contains(a) && s2.contains(a)); +} + +/// The set difference between two finite sets is finite. +pub broadcast proof fn lemma_iset_difference_finite(s1: ISet, s2: ISet) + requires + s1.finite(), + ensures + #[trigger] s1.difference(s2).finite(), +{ + assert(forall|a| + #![all_triggers] + s1.difference(s2).contains(a) ==> s1.contains(a) && !s2.contains(a)); +} + +/// An infinite set `s` contains the element `s.choose()`. +pub broadcast proof fn lemma_iset_choose_infinite(s: ISet) + requires + !s.finite(), + ensures + #[trigger] s.contains(s.choose()), +{ + let f = |a: A| 0; + let ub = 0; + let _ = trigger_finite(f, ub); +} + +// Trusted axioms about len +// Note: we could add more axioms about len, but they would be incomplete. +// The following, with lemma_iset_ext_equal, are enough to build libraries about len. +/// The empty set has length 0. +pub broadcast proof fn lemma_iset_empty_len() + ensures + #[trigger] ISet::::empty().len() == 0, +{ + fold::lemma_fold_empty(0, |b: nat, a: A| b + 1); +} + +/// The result of inserting an element `a` into a finite set `s` has length +/// `s.len() + 1` if `a` is not already in `s` and length `s.len()` otherwise. +pub broadcast proof fn lemma_iset_insert_len(s: ISet, a: A) + requires + s.finite(), + ensures + #[trigger] s.insert(a).len() == s.len() + (if s.contains(a) { + 0int + } else { + 1 + }), +{ + if s.contains(a) { + assert(s =~= s.insert(a)); + } else { + fold::lemma_fold_insert(s, 0, |b: nat, a: A| b + 1, a); + } +} + +/// The result of removing an element `a` from a finite set `s` has length +/// `s.len() - 1` if `a` is in `s` and length `s.len()` otherwise. +pub broadcast proof fn lemma_iset_remove_len(s: ISet, a: A) + requires + s.finite(), + ensures + s.len() == #[trigger] s.remove(a).len() + (if s.contains(a) { + 1int + } else { + 0 + }), +{ + lemma_iset_remove_finite(s, a); + lemma_iset_insert_len(s.remove(a), a); + if s.contains(a) { + assert(s =~= s.remove(a).insert(a)); + } else { + assert(s =~= s.remove(a)); + } +} + +/// If a finite set `s` contains any element, it has length greater than 0. +pub broadcast proof fn lemma_iset_contains_len(s: ISet, a: A) + requires + s.finite(), + #[trigger] s.contains(a), + ensures + #[trigger] s.len() != 0, +{ + let a = s.choose(); + assert(s.remove(a).insert(a) =~= s); + lemma_iset_remove_finite(s, a); + lemma_iset_insert_finite(s.remove(a), a); + lemma_iset_insert_len(s.remove(a), a); +} + +/// A finite set `s` contains the element `s.choose()` if it has length greater than 0. +pub broadcast proof fn lemma_iset_choose_len(s: ISet) + requires + s.finite(), + #[trigger] s.len() != 0, + ensures + #[trigger] s.contains(s.choose()), +{ + // Separate statements to work around https://github.com/verus-lang/verusfmt/issues/86 + broadcast use lemma_iset_contains_len; + broadcast use lemma_iset_empty_len; + broadcast use lemma_iset_ext_equal; + broadcast use lemma_iset_insert_finite; + + let pred = |s: ISet| s.finite() ==> s.len() == 0 <==> s =~= ISet::empty(); + fold::lemma_finite_set_induct(s, pred); +} + +pub proof fn lemma_iset_finite_if_subset_of_seq(i: ISet, s: Seq) + requires + forall|a| i.contains(a) ==> s.contains(a), + ensures + i.finite(), +{ + let f = |a: A| (s.index_of(a) as nat); + let ub = s.len(); + assert(surj_on(f, i)) by { + assert forall|a1, a2| + #![all_triggers] + i.contains(a1) && i.contains(a2) && a1 != a2 implies f(a1) != f(a2) by { + assert(s.contains(a1)); + assert(s.contains(a2)); + assert(0 <= f(a1) < s.len() && s[f(a1) as int] == a1); + assert(0 <= f(a2) < s.len() && s[f(a2) as int] == a2); + } + } + assert forall|a| i.contains(a) implies f(a) < ub by { + assert(s.contains(a)); + assert(0 <= f(a) < s.len() && s[f(a) as int] == a); + } + assert(trigger_finite(f, ub)); +} + +pub broadcast group group_iset_lemmas { + lemma_iset_empty, + lemma_iset_new, + lemma_iset_insert_same, + lemma_iset_insert_different, + lemma_iset_remove_same, + lemma_iset_remove_insert, + lemma_iset_remove_different, + lemma_iset_union, + lemma_iset_intersect, + lemma_iset_difference, + lemma_iset_complement, + lemma_iset_ext_equal, + lemma_iset_ext_equal_eq, + lemma_iset_ext_equal_deep, + lemma_iset_mk_map_domain, + lemma_iset_mk_map_index, + lemma_iset_empty_finite, + lemma_iset_insert_finite, + lemma_iset_remove_finite, + lemma_iset_union_finite, + lemma_iset_intersect_finite, + lemma_iset_difference_finite, + lemma_iset_choose_infinite, + lemma_iset_empty_len, + lemma_iset_insert_len, + lemma_iset_remove_len, + lemma_iset_contains_len, + lemma_iset_choose_len, +} + +// Macros +#[doc(hidden)] +#[macro_export] +macro_rules! iset_internal { + [$($elem:expr),* $(,)?] => { + $crate::vstd::iset::ISet::empty() + $(.insert($elem))* + }; +} + +#[macro_export] +macro_rules! iset { + [$($tail:tt)*] => { + $crate::vstd::prelude::verus_proof_macro_exprs!($crate::vstd::iset::iset_internal!($($tail)*)) + }; +} + +pub use iset_internal; +pub use iset; + +} // verus! diff --git a/source/vstd/iset_lib.rs b/source/vstd/iset_lib.rs new file mode 100644 index 0000000000..fdac5ba1aa --- /dev/null +++ b/source/vstd/iset_lib.rs @@ -0,0 +1,1541 @@ +#[allow(unused_imports)] +use super::iset::*; +#[allow(unused_imports)] +use super::multiset::Multiset; +#[allow(unused_imports)] +use super::pervasive::*; +use super::prelude::Seq; +#[allow(unused_imports)] +use super::prelude::*; +#[allow(unused_imports)] +use super::relations::*; +#[allow(unused_imports)] +use super::set::*; + +verus! { + +broadcast use super::iset::group_iset_lemmas; + +impl ISet { + /// Is `true` if called by a "full" set, i.e., a set containing every element of type `A`. + pub open spec fn is_full(self) -> bool { + self == ISet::::full() + } + + /// Is `true` if called by an "empty" set, i.e., a set containing no elements and has length 0 + pub open spec fn is_empty(self) -> (b: bool) { + self =~= ISet::::empty() + } + + /// Returns the set contains an element `f(x)` for every element `x` in `self`. + pub open spec fn map(self, f: spec_fn(A) -> B) -> ISet { + ISet::new(|a: B| exists|x: A| self.contains(x) && a == f(x)) + } + + /// `ISet::map_by` is like `ISet::map`, but `map` only takes a forward function `fwd: spec_fn(A) -> B`, + /// while `map_by` also takes a reverse function `rev: spec_fn(B) -> A` + /// such that `rev(fwd(a)) == a`. + /// When `fwd` has such a reverse function, `ISet::map_by` can make proofs easier + /// by avoiding the "exists" that appears in lemmas about `ISet::map`. + /// Example: for a set `s: ISet`, to map each `i` in `s` to `(i, 10 * i)`, + /// we can write either `s.map(|i: int| (i, 10 * i))` + /// or `s.map_by(|i: int| (i, 10 * i), |p: (int, int)| p.0)`; + /// the version with `map_by` is usually easier to use in proofs. + /// If the recommendation `forall|a: A| self.contains(a) ==> rev(fwd(a)) == a` is satisfied, + /// it is trivially guaranteed that `self.map_by(fwd, rev) == self.map(fwd)`. + /// Also see the `set_build!` macro for a convenient interface to `map_by`. + pub open spec fn map_by(self, fwd: spec_fn(A) -> B, rev: spec_fn(B) -> A) -> ISet + recommends + forall|a: A| self.contains(a) ==> rev(fwd(a)) == a, + { + ISet::new(|b: B| self.contains(rev(b)) && b == fwd(rev(b))) + } + + /// Similar to `ISet::map_by`, but the forward function returns `ISet` rather than `B`, + /// and `map_flatten_by` flattens the final result from `ISet>` to just `ISet`. + /// This can be easier to work with in proofs than calling `map` and `flatten` separately, + /// since `map` and `flatten` introduce "exists", while `map_flatten_by` does not. + /// Also see the `set_build!` macro for a convenient interface to `map_flatten_by`. + pub open spec fn map_flatten_by( + self, + fwd: spec_fn(A) -> ISet, + rev: spec_fn(B) -> A, + ) -> ISet + recommends + forall|a: A, b: B| #[trigger] + self.contains(a) && fwd(a).contains(b) ==> #[trigger] rev(b) == a, + { + ISet::new(|b: B| self.contains(rev(b)) && fwd(rev(b)).contains(b)) + } + + /// This proof demonstrates that calling `map_flatten_by` is equivalent to + /// first calling `map`, then calling `flatten`. + pub proof fn map_flatten_by_is_map_flatten( + self, + fwd: spec_fn(A) -> ISet, + rev: spec_fn(B) -> A, + ) + requires + forall|a: A, b: B| #[trigger] + self.contains(a) && fwd(a).contains(b) ==> #[trigger] rev(b) == a, + ensures + self.map_flatten_by(fwd, rev) == self.map(fwd).flatten(), + { + assert forall|b: B| self.map_flatten_by(fwd, rev).contains(b) implies #[trigger] self.map( + fwd, + ).flatten().contains(b) by { + let bs = choose|bs: ISet| + (exists|a: A| self.contains(a) && bs == fwd(a)) && bs.contains(b); + assert(self.map(fwd).contains(bs) <==> (exists|a: A| self.contains(a) && bs == fwd(a))); + } + } + + /// Converts a set into a sequence with an arbitrary ordering. + pub open spec fn to_seq(self) -> Seq + recommends + self.finite(), + decreases self.len(), + when self.finite() + { + if self.len() == 0 { + Seq::::empty() + } else { + let x = self.choose(); + Seq::::empty().push(x) + self.remove(x).to_seq() + } + } + + /// Converts a set into a sequence sorted by the given ordering function `leq` + pub open spec fn to_sorted_seq(self, leq: spec_fn(A, A) -> bool) -> Seq { + self.to_seq().sort_by(leq) + } + + /// A singleton set has at least one element and any two elements are equal. + pub open spec fn is_singleton(self) -> bool { + &&& self.len() > 0 + &&& (forall|x: A, y: A| self.contains(x) && self.contains(y) ==> x == y) + } + + /// Indicates if the function given by `r` is injective on this set, + /// i.e., whether each element of this set is mapped to a different + /// value by `r`. + pub open spec fn injective_on(self, r: spec_fn(A) -> B) -> bool { + forall|x1: A, x2: A| + self.contains(x1) && self.contains(x2) && #[trigger] r(x1) == #[trigger] r(x2) ==> x1 + == x2 + } + + /// An element in an ordered set is called a least element (or a minimum), if it is less than + /// every other element of the set. + pub open spec fn has_least(self, leq: spec_fn(A, A) -> bool, min: A) -> bool { + self.contains(min) && forall|x: A| self.contains(x) ==> #[trigger] leq(min, x) + } + + /// An element in an ordered set is called a minimal element, if no other element is less than it. + pub open spec fn has_minimum(self, leq: spec_fn(A, A) -> bool, min: A) -> bool { + self.contains(min) && forall|x: A| + self.contains(x) && #[trigger] leq(x, min) ==> #[trigger] leq(min, x) + } + + /// An element in an ordered set is called a greatest element (or a maximum), if it is greater than + ///every other element of the set. + pub open spec fn has_greatest(self, leq: spec_fn(A, A) -> bool, max: A) -> bool { + self.contains(max) && forall|x: A| self.contains(x) ==> #[trigger] leq(x, max) + } + + /// An element in an ordered set is called a maximal element, if no other element is greater than it. + pub open spec fn has_maximum(self, leq: spec_fn(A, A) -> bool, max: A) -> bool { + self.contains(max) && forall|x: A| + self.contains(x) && #[trigger] leq(max, x) ==> #[trigger] leq(x, max) + } + + /// If a function is injective on the set `self`, then it is also injective on any subset `other` of `self`. + pub proof fn lemma_injective_on_subset(self, r: spec_fn(A) -> B, other: Self) + requires + other <= self, + self.injective_on(r), + ensures + other.injective_on(r), + { + assert forall|a1: A, a2: A| + other.contains(a1) && other.contains(a2) && #[trigger] r(a1) == #[trigger] r( + a2, + ) implies a1 == a2 by { + assert(self.contains(a1)); + assert(self.contains(a2)); + assert(r(a1) == r(a2)); + } + } + + /// Any totally-ordered set contains a unique minimal (equivalently, least) element. + /// Returns an arbitrary value if r is not a total ordering + pub closed spec fn find_unique_minimal(self, r: spec_fn(A, A) -> bool) -> A + recommends + total_ordering(r), + self.len() > 0, + self.finite(), + decreases self.len(), + when self.finite() + { + proof { + broadcast use group_iset_properties; + + } + if self.len() <= 1 { + self.choose() + } else { + let x = choose|x: A| self.contains(x); + let min = self.remove(x).find_unique_minimal(r); + if r(min, x) { + min + } else { + x + } + } + } + + /// Proof of correctness and expected behavior for `ISet::find_unique_minimal`. + pub proof fn find_unique_minimal_ensures(self, r: spec_fn(A, A) -> bool) + requires + self.finite(), + self.len() > 0, + total_ordering(r), + ensures + self.has_minimum(r, self.find_unique_minimal(r)) && (forall|min: A| + self.has_minimum(r, min) ==> self.find_unique_minimal(r) == min), + decreases self.len(), + { + broadcast use group_iset_properties; + + if self.len() == 1 { + let x = choose|x: A| self.contains(x); + assert(self.remove(x).insert(x) =~= self); + assert(self.has_minimum(r, self.find_unique_minimal(r))); + } else { + let x = choose|x: A| self.contains(x); + self.remove(x).find_unique_minimal_ensures(r); + assert(self.remove(x).has_minimum(r, self.remove(x).find_unique_minimal(r))); + let y = self.remove(x).find_unique_minimal(r); + let min_updated = self.find_unique_minimal(r); + assert(!r(y, x) ==> min_updated == x); + assert(forall|elt: A| + self.remove(x).contains(elt) && #[trigger] r(elt, y) ==> #[trigger] r(y, elt)); + assert forall|elt: A| + self.contains(elt) && #[trigger] r(elt, min_updated) implies #[trigger] r( + min_updated, + elt, + ) by { + assert(r(min_updated, x) || r(min_updated, y)); + if min_updated == y { // Case where the new min is the old min + assert(self.has_minimum(r, self.find_unique_minimal(r))); + } else { //Case where the new min is the newest element + assert(self.remove(x).contains(elt) || elt == x); + assert(min_updated == x); + assert(r(x, y) || r(y, x)); + assert(!r(x, y) || !r(y, x)); + assert(!(min_updated == y) ==> !r(y, x)); + assert(r(x, y)); + if (self.remove(x).contains(elt)) { + assert(r(elt, y) && r(y, elt) ==> elt == y); + } else { + } + } + } + assert forall|min_poss: A| + self.has_minimum(r, min_poss) implies self.find_unique_minimal(r) == min_poss by { + assert(self.remove(x).has_minimum(r, min_poss) || x == min_poss); + if self.remove(x).has_minimum(r, min_poss) { + assert(r(x, min_poss) ==> r(min_poss, x)); + assert(r(min_poss, self.find_unique_minimal(r))); + } else { + assert(x == min_poss); + assert(r(x, y)); + } + } + } + } + + /// Any totally-ordered set contains a unique maximal (equivalently, greatest) element. + /// Returns an arbitrary value if r is not a total ordering + pub closed spec fn find_unique_maximal(self, r: spec_fn(A, A) -> bool) -> A + recommends + total_ordering(r), + self.len() > 0, + decreases self.len(), + when self.finite() + { + proof { + broadcast use group_iset_properties; + + } + if self.len() <= 1 { + self.choose() + } else { + let x = choose|x: A| self.contains(x); + let max = self.remove(x).find_unique_maximal(r); + if r(x, max) { + max + } else { + x + } + } + } + + /// Proof of correctness and expected behavior for `ISet::find_unique_maximal`. + pub proof fn find_unique_maximal_ensures(self, r: spec_fn(A, A) -> bool) + requires + self.finite(), + self.len() > 0, + total_ordering(r), + ensures + self.has_maximum(r, self.find_unique_maximal(r)) && (forall|max: A| + self.has_maximum(r, max) ==> self.find_unique_maximal(r) == max), + decreases self.len(), + { + broadcast use group_iset_properties; + + if self.len() == 1 { + let x = choose|x: A| self.contains(x); + assert(self.remove(x) =~= ISet::::empty()); + assert(self.contains(self.find_unique_maximal(r))); + } else { + let x = choose|x: A| self.contains(x); + self.remove(x).find_unique_maximal_ensures(r); + assert(self.remove(x).has_maximum(r, self.remove(x).find_unique_maximal(r))); + assert(self.remove(x).insert(x) =~= self); + let y = self.remove(x).find_unique_maximal(r); + let max_updated = self.find_unique_maximal(r); + assert(max_updated == x || max_updated == y); + assert(!r(x, y) ==> max_updated == x); + assert forall|elt: A| + self.contains(elt) && #[trigger] r(max_updated, elt) implies #[trigger] r( + elt, + max_updated, + ) by { + assert(r(x, max_updated) || r(y, max_updated)); + if max_updated == y { // Case where the new max is the old max + assert(r(elt, max_updated)); + assert(r(x, max_updated)); + assert(self.has_maximum(r, self.find_unique_maximal(r))); + } else { //Case where the new max is the newest element + assert(self.remove(x).contains(elt) || elt == x); + assert(max_updated == x); + assert(r(x, y) || r(y, x)); + assert(!r(x, y) || !r(y, x)); + assert(!(max_updated == y) ==> !r(x, y)); + assert(r(y, x)); + if (self.remove(x).contains(elt)) { + assert(r(y, elt) ==> r(elt, y)); + assert(r(y, elt) && r(elt, y) ==> elt == y); + assert(r(elt, x)); + assert(r(elt, max_updated)) + } else { + } + } + } + assert forall|max_poss: A| + self.has_maximum(r, max_poss) implies self.find_unique_maximal(r) == max_poss by { + assert(self.remove(x).has_maximum(r, max_poss) || x == max_poss); + assert(r(max_poss, self.find_unique_maximal(r))); + assert(r(self.find_unique_maximal(r), max_poss)); + } + } + } + + /// Converts a set into a multiset where each element from the set has + /// multiplicity 1 and any other element has multiplicity 0. + pub open spec fn to_multiset(self) -> Multiset + decreases self.len(), + when self.finite() + { + if self.len() == 0 { + Multiset::::empty() + } else { + Multiset::::empty().insert(self.choose()).add( + self.remove(self.choose()).to_multiset(), + ) + } + } + + /// A finite set with length 0 is equivalent to the empty set. + pub proof fn lemma_len0_is_empty(self) + requires + self.finite(), + self.len() == 0, + ensures + self == ISet::::empty(), + { + if exists|a: A| self.contains(a) { + // derive contradiction: + assert(self.remove(self.choose()).len() + 1 == 0); + } + assert(self =~= ISet::empty()); + } + + /// A singleton set has length 1. + pub proof fn lemma_singleton_size(self) + requires + self.is_singleton(), + ensures + self.len() == 1, + { + broadcast use group_iset_properties; + + assert(self.remove(self.choose()) =~= ISet::empty()); + } + + /// A set has exactly one element, if and only if, it has at least one element and any two elements are equal. + pub proof fn lemma_is_singleton(s: ISet) + requires + s.finite(), + ensures + s.is_singleton() == (s.len() == 1), + { + if s.is_singleton() { + s.lemma_singleton_size(); + } + if s.len() == 1 { + assert forall|x: A, y: A| s.contains(x) && s.contains(y) implies x == y by { + let x = choose|x: A| s.contains(x); + broadcast use group_iset_properties; + + assert(s.remove(x).len() == 0); + assert(s.insert(x) =~= s); + } + } + } + + /// The result of filtering a finite set is finite and has size less than or equal to the original set. + pub proof fn lemma_len_filter(self, f: spec_fn(A) -> bool) + requires + self.finite(), + ensures + self.filter(f).finite(), + self.filter(f).len() <= self.len(), + decreases self.len(), + { + lemma_len_intersect::(self, ISet::new(f)); + } + + /// In a pre-ordered set, a greatest element is necessarily maximal. + pub proof fn lemma_greatest_implies_maximal(self, r: spec_fn(A, A) -> bool, max: A) + requires + pre_ordering(r), + ensures + self.has_greatest(r, max) ==> self.has_maximum(r, max), + { + } + + /// In a pre-ordered set, a least element is necessarily minimal. + pub proof fn lemma_least_implies_minimal(self, r: spec_fn(A, A) -> bool, min: A) + requires + pre_ordering(r), + ensures + self.has_least(r, min) ==> self.has_minimum(r, min), + { + } + + /// In a totally-ordered set, an element is maximal if and only if it is a greatest element. + pub proof fn lemma_maximal_equivalent_greatest(self, r: spec_fn(A, A) -> bool, max: A) + requires + total_ordering(r), + ensures + self.has_greatest(r, max) <==> self.has_maximum(r, max), + { + assert(self.has_maximum(r, max) ==> forall|x: A| + !self.contains(x) || !r(max, x) || r(x, max)); + } + + /// In a totally-ordered set, an element is maximal if and only if it is a greatest element. + pub proof fn lemma_minimal_equivalent_least(self, r: spec_fn(A, A) -> bool, min: A) + requires + total_ordering(r), + ensures + self.has_least(r, min) <==> self.has_minimum(r, min), + { + assert(self.has_minimum(r, min) ==> forall|x: A| + !self.contains(x) || !r(x, min) || r(min, x)); + } + + /// In a partially-ordered set, there exists at most one least element. + pub proof fn lemma_least_is_unique(self, r: spec_fn(A, A) -> bool) + requires + partial_ordering(r), + ensures + forall|min: A, min_prime: A| + self.has_least(r, min) && self.has_least(r, min_prime) ==> min == min_prime, + { + assert forall|min: A, min_prime: A| + self.has_least(r, min) && self.has_least(r, min_prime) implies min == min_prime by { + assert(r(min, min_prime)); + assert(r(min_prime, min)); + } + } + + /// In a partially-ordered set, there exists at most one greatest element. + pub proof fn lemma_greatest_is_unique(self, r: spec_fn(A, A) -> bool) + requires + partial_ordering(r), + ensures + forall|max: A, max_prime: A| + self.has_greatest(r, max) && self.has_greatest(r, max_prime) ==> max == max_prime, + { + assert forall|max: A, max_prime: A| + self.has_greatest(r, max) && self.has_greatest(r, max_prime) implies max + == max_prime by { + assert(r(max_prime, max)); + assert(r(max, max_prime)); + } + } + + /// In a totally-ordered set, there exists at most one minimal element. + pub proof fn lemma_minimal_is_unique(self, r: spec_fn(A, A) -> bool) + requires + total_ordering(r), + ensures + forall|min: A, min_prime: A| + self.has_minimum(r, min) && self.has_minimum(r, min_prime) ==> min == min_prime, + { + assert forall|min: A, min_prime: A| + self.has_minimum(r, min) && self.has_minimum(r, min_prime) implies min == min_prime by { + self.lemma_minimal_equivalent_least(r, min); + self.lemma_minimal_equivalent_least(r, min_prime); + self.lemma_least_is_unique(r); + } + } + + /// In a totally-ordered set, there exists at most one maximal element. + pub proof fn lemma_maximal_is_unique(self, r: spec_fn(A, A) -> bool) + requires + self.finite(), + total_ordering(r), + ensures + forall|max: A, max_prime: A| + self.has_maximum(r, max) && self.has_maximum(r, max_prime) ==> max == max_prime, + { + assert forall|max: A, max_prime: A| + self.has_maximum(r, max) && self.has_maximum(r, max_prime) implies max == max_prime by { + self.lemma_maximal_equivalent_greatest(r, max); + self.lemma_maximal_equivalent_greatest(r, max_prime); + self.lemma_greatest_is_unique(r); + } + } + + /// ISet difference with an additional element inserted decreases the size of + /// the result. This can be useful for proving termination when traversing + /// a set while tracking the elements that have already been handled. + pub broadcast proof fn lemma_iset_insert_diff_decreases(self, s: ISet, elt: A) + requires + self.contains(elt), + !s.contains(elt), + self.finite(), + ensures + #[trigger] self.difference(s.insert(elt)).len() < self.difference(s).len(), + { + self.difference(s.insert(elt)).lemma_subset_not_in_lt(self.difference(s), elt); + } + + /// If there is an element not present in a subset, its length is stricly smaller. + pub proof fn lemma_subset_not_in_lt(self: ISet, s2: ISet, elt: A) + requires + self.subset_of(s2), + s2.finite(), + !self.contains(elt), + s2.contains(elt), + ensures + self.len() < s2.len(), + { + let s2_no_elt = s2.remove(elt); + assert(self.len() <= s2_no_elt.len()) by { + lemma_len_subset(self, s2_no_elt); + } + } + + /// Inserting an element and mapping a function over a set commute + pub broadcast proof fn lemma_iset_map_insert_commute(self, elt: A, f: spec_fn(A) -> B) + ensures + #[trigger] self.insert(elt).map(f) =~= self.map(f).insert(f(elt)), + { + assert forall|x: B| self.map(f).insert(f(elt)).contains(x) implies self.insert(elt).map( + f, + ).contains(x) by { + if x == f(elt) { + assert(self.insert(elt).contains(elt)); + } else { + let y = choose|y: A| self.contains(y) && f(y) == x; + assert(self.insert(elt).contains(y)); + } + } + } + + /// `map` and `union` commute + pub proof fn lemma_map_union_commute(self, t: ISet, f: spec_fn(A) -> B) + ensures + (self.union(t)).map(f) =~= self.map(f).union(t.map(f)), + { + broadcast use group_iset_lemmas; + + let lhs = self.union(t).map(f); + let rhs = self.map(f).union(t.map(f)); + + assert forall|elem: B| rhs.contains(elem) implies lhs.contains(elem) by { + if self.map(f).contains(elem) { + let preimage = choose|preimage: A| self.contains(preimage) && f(preimage) == elem; + assert(self.union(t).contains(preimage)); + } else { + assert(t.map(f).contains(elem)); + let preimage = choose|preimage: A| t.contains(preimage) && f(preimage) == elem; + assert(self.union(t).contains(preimage)); + } + } + } + + /// Utility function for more concise universal quantification over sets + pub open spec fn all(&self, pred: spec_fn(A) -> bool) -> bool { + forall|x: A| self.contains(x) ==> pred(x) + } + + /// Utility function for more concise existential quantification over sets + pub open spec fn any(&self, pred: spec_fn(A) -> bool) -> bool { + exists|x: A| self.contains(x) && pred(x) + } + + /// `any` is preserved between predicates `p` and `q` if `p` implies `q`. + pub broadcast proof fn lemma_any_map_preserved_pred( + self, + p: spec_fn(A) -> bool, + q: spec_fn(B) -> bool, + f: spec_fn(A) -> B, + ) + requires + #[trigger] self.any(p), + forall|x: A| #[trigger] p(x) ==> q(f(x)), + ensures + #[trigger] self.map(f).any(q), + { + let x = choose|x: A| self.contains(x) && p(x); + assert(self.map(f).contains(f(x))); + } + + /// Collecting all elements `b` where `f` returns `Some(b)` + pub open spec fn filter_map(self, f: spec_fn(A) -> Option) -> ISet { + self.map( + |elem: A| + match f(elem) { + Option::Some(r) => iset!{r}, + Option::None => iset!{}, + }, + ).flatten() + } + + /// Inserting commutes with `filter_map` + pub broadcast proof fn lemma_filter_map_insert( + s: ISet, + f: spec_fn(A) -> Option, + elem: A, + ) + ensures + #[trigger] s.insert(elem).filter_map(f) == (match f(elem) { + Some(res) => s.filter_map(f).insert(res), + None => s.filter_map(f), + }), + { + broadcast use group_iset_lemmas; + broadcast use ISet::lemma_iset_map_insert_commute; + + let lhs = s.insert(elem).filter_map(f); + let rhs = match f(elem) { + Some(res) => s.filter_map(f).insert(res), + None => s.filter_map(f), + }; + let to_set = |elem: A| + match f(elem) { + Option::Some(r) => iset!{r}, + Option::None => iset!{}, + }; + assert forall|r: B| #[trigger] lhs.contains(r) implies rhs.contains(r) by { + if f(elem) != Some(r) { + let orig = choose|orig: A| #[trigger] + s.contains(orig) && f(orig) == Option::Some(r); + assert(to_set(orig) == iset!{r}); + assert(s.map(to_set).contains(to_set(orig))); + } + } + assert forall|r: B| #[trigger] rhs.contains(r) implies lhs.contains(r) by { + if Some(r) == f(elem) { + assert(s.insert(elem).map(to_set).contains(to_set(elem))); + } else { + let orig = choose|orig: A| #[trigger] + s.contains(orig) && f(orig) == Option::Some(r); + assert(s.insert(elem).map(to_set).contains(to_set(orig))); + } + } + assert(lhs =~= rhs); + } + + /// `filter_map` and `union` commute. + pub broadcast proof fn lemma_filter_map_union(self, f: spec_fn(A) -> Option, t: ISet) + ensures + #[trigger] self.union(t).filter_map(f) == self.filter_map(f).union(t.filter_map(f)), + { + broadcast use group_iset_lemmas; + + let lhs = self.union(t).filter_map(f); + let rhs = self.filter_map(f).union(t.filter_map(f)); + let to_set = |elem: A| + match f(elem) { + Option::Some(r) => iset!{r}, + Option::None => iset!{}, + }; + + assert forall|elem: B| rhs.contains(elem) implies lhs.contains(elem) by { + if self.filter_map(f).contains(elem) { + let x = choose|x: A| self.contains(x) && f(x) == Option::Some(elem); + assert(self.union(t).contains(x)); + assert(self.union(t).map(to_set).contains(to_set(x))); + } + if t.filter_map(f).contains(elem) { + let x = choose|x: A| t.contains(x) && f(x) == Option::Some(elem); + assert(self.union(t).contains(x)); + assert(self.union(t).map(to_set).contains(to_set(x))); + } + } + assert forall|elem: B| lhs.contains(elem) implies rhs.contains(elem) by { + let x = choose|x: A| self.union(t).contains(x) && f(x) == Option::Some(elem); + if self.contains(x) { + assert(self.map(to_set).contains(to_set(x))); + assert(self.filter_map(f).contains(elem)); + } else { + assert(t.contains(x)); + assert(t.map(to_set).contains(to_set(x))); + assert(t.filter_map(f).contains(elem)); + } + } + assert(lhs =~= rhs); + } + + /// `map` preserves finiteness + pub proof fn lemma_map_finite(self, f: spec_fn(A) -> B) + requires + self.finite(), + ensures + self.map(f).finite(), + decreases self.len(), + { + broadcast use group_iset_lemmas; + broadcast use lemma_iset_empty_equivalency_len; + + if self.len() == 0 { + assert(forall|elem: A| !(#[trigger] self.contains(elem))); + assert forall|res: B| #[trigger] self.map(f).contains(res) implies false by { + let x = choose|x: A| self.contains(x) && f(x) == res; + } + assert(self.map(f) =~= ISet::::empty()); + } else { + let x = choose|x: A| self.contains(x); + assert(self.map(f).contains(f(x))); + self.remove(x).lemma_map_finite(f); + assert(self.remove(x).insert(x) == self); + assert(self.map(f) == self.remove(x).map(f).insert(f(x))); + } + } + + /// `map_by` preserves finiteness. + pub broadcast proof fn lemma_map_by_finite(self, fwd: spec_fn(A) -> B, rev: spec_fn(B) -> A) + requires + self.finite(), + ensures + #[trigger] self.map_by(fwd, rev).finite(), + { + broadcast use lemma_iset_subset_finite; + + assert(self.map_by(fwd, rev).subset_of(self.map(fwd))); + self.lemma_map_finite(fwd); + } + + /// `map_flatten_by` preserves finiteness. + pub broadcast proof fn lemma_map_flatten_by_finite( + self, + fwd: spec_fn(A) -> ISet, + rev: spec_fn(B) -> A, + ) + requires + self.finite(), + forall|a: A| self.contains(a) ==> fwd(a).finite(), + ensures + #[trigger] self.map_flatten_by(fwd, rev).finite(), + { + broadcast use lemma_iset_subset_finite; + + let s1 = self.map_flatten_by(fwd, rev); + let s2 = self.map(fwd).flatten(); + assert forall|b: B| s1.contains(b) implies s2.contains(b) by { + assert(self.map(fwd).contains(fwd(rev(b)))); + } + assert(s1.subset_of(s2)); + self.lemma_map_finite(fwd); + self.map(fwd).lemma_flatten_finite(); + } + + /// If `self` is a subset of `s2`, and all elements of `s2` + /// satisfy predicate `p`, then all elements of `self` satisfy + /// predicate `p`. + pub broadcast proof fn lemma_iset_all_subset(self, s2: ISet, p: spec_fn(A) -> bool) + requires + #[trigger] self.subset_of(s2), + s2.all(p), + ensures + #[trigger] self.all(p), + { + broadcast use group_iset_lemmas; + + } + + /// `filter_map` preserves finiteness. + pub broadcast proof fn lemma_filter_map_finite(self, f: spec_fn(A) -> Option) + requires + self.finite(), + ensures + #[trigger] self.filter_map(f).finite(), + decreases self.len(), + { + broadcast use group_iset_lemmas; + broadcast use ISet::lemma_filter_map_insert; + + let mapped = self.filter_map(f); + if self.len() == 0 { + assert(self.filter_map(f) =~= ISet::::empty()); + } else { + let elem = self.choose(); + self.remove(elem).lemma_filter_map_finite(f); + assert(self =~= self.remove(elem).insert(elem)); + } + } + + /// Conversion to a sequence and back to a set is the identity function. + pub broadcast proof fn lemma_to_seq_to_set_id(self) + requires + self.finite(), + ensures + #[trigger] self.to_seq().to_set().to_iset() =~= self, + decreases self.len(), + { + broadcast use group_set_lemmas; + broadcast use group_iset_lemmas; + broadcast use lemma_iset_empty_equivalency_len; + broadcast use super::seq_lib::group_seq_lib_default; + broadcast use super::seq_lib::group_seq_properties; + + if self.len() == 0 { + assert(self.to_seq().to_set().to_iset() =~= ISet::::empty()); + } else { + let elem = self.choose(); + self.remove(elem).lemma_to_seq_to_set_id(); + assert(self =~= self.remove(elem).insert(elem)); + assert(self.to_seq().to_set() =~= self.remove(elem).to_seq().to_set().insert(elem)); + } + } +} + +impl ISet> { + /// This function creates a set from all the elements of all the elements + /// of `self`. + pub open spec fn flatten(self) -> ISet { + ISet::new( + |elem| + exists|elem_s: ISet| #[trigger] self.contains(elem_s) && elem_s.contains(elem), + ) + } + + /// Flattening then unioning with another set is equivalent to + /// inserting that other set and then flattening. + pub broadcast proof fn flatten_insert_union_commute(self, other: ISet) + ensures + self.flatten().union(other) =~= #[trigger] self.insert(other).flatten(), + { + broadcast use group_iset_lemmas; + + let lhs = self.flatten().union(other); + let rhs = self.insert(other).flatten(); + + assert forall|elem: A| lhs.contains(elem) implies rhs.contains(elem) by { + if exists|s: ISet| self.contains(s) && s.contains(elem) { + let s = choose|s: ISet| self.contains(s) && s.contains(elem); + assert(self.insert(other).contains(s)); + assert(s.contains(elem)); + } else { + assert(self.insert(other).contains(other)); + } + } + } + + /// If `self` is finite and all its elements are finite, then `self.flatten()` + /// is also finite. + pub proof fn lemma_flatten_finite(self) + requires + self.finite(), + forall|s: ISet| self.contains(s) ==> #[trigger] s.finite(), + ensures + self.flatten().finite(), + decreases self.len(), + { + broadcast use group_iset_lemmas; + + if self.len() == 0 { + assert(self.flatten() =~= ISet::::empty()); + } else { + let s = self.choose(); + let self2 = self.remove(s); + self2.lemma_flatten_finite(); + self2.flatten_insert_union_commute(s); + } + } +} + +/// Two sets are equal iff mapping `f` results in equal sets, if `f` is injective. +pub proof fn lemma_isets_eq_iff_injective_map_eq(s1: ISet, s2: ISet, f: spec_fn(T) -> S) + requires + super::relations::injective(f), + ensures + (s1 == s2) <==> (s1.map(f) == s2.map(f)), +{ + broadcast use group_iset_lemmas; + + if (s1.map(f) == s2.map(f)) { + assert(s1.map(f).len() == s2.map(f).len()); + if !s1.subset_of(s2) { + let x = choose|x: T| s1.contains(x) && !s2.contains(x); + assert(s1.map(f).contains(f(x))); + } else if !s2.subset_of(s1) { + let x = choose|x: T| s2.contains(x) && !s1.contains(x); + assert(s2.map(f).contains(f(x))); + } + assert(s1 =~= s2); + } +} + +/// Two sets are equal iff applying an injective (in the union of the sets) function `f` to each set produces equal sets. +pub proof fn lemma_isets_eq_iff_injective_map_on_eq( + s1: ISet, + s2: ISet, + f: spec_fn(T) -> S, +) + requires + (s1 + s2).injective_on(f), + ensures + (s1 == s2) <==> (s1.map(f) == s2.map(f)), +{ + broadcast use group_iset_lemmas; + + if (s1.map(f) == s2.map(f)) { + assert(s1.map(f).len() == s2.map(f).len()); + if !s1.subset_of(s2) { + let x = choose|x: T| s1.contains(x) && !s2.contains(x); + assert(s1.map(f).contains(f(x))); + } else if !s2.subset_of(s1) { + let x = choose|x: T| s2.contains(x) && !s1.contains(x); + assert(s2.map(f).contains(f(x))); + } + assert(s1 =~= s2); + } +} + +/// The result of inserting an element `a` into a set `s` is finite iff `s` is finite. +pub broadcast proof fn lemma_iset_insert_finite_iff(s: ISet, a: A) + ensures + #[trigger] s.insert(a).finite() <==> s.finite(), +{ + if s.insert(a).finite() { + if s.contains(a) { + assert(s == s.insert(a)); + } else { + assert(s == s.insert(a).remove(a)); + } + } + assert(s.insert(a).finite() ==> s.finite()); +} + +/// The result of removing an element `a` into a set `s` is finite iff `s` is finite. +pub broadcast proof fn lemma_iset_remove_finite_iff(s: ISet, a: A) + ensures + #[trigger] s.remove(a).finite() <==> s.finite(), +{ + if s.remove(a).finite() { + if s.contains(a) { + assert(s == s.remove(a).insert(a)); + } else { + assert(s == s.remove(a)); + } + } +} + +/// The union of two sets is finite iff both sets are finite. +pub broadcast proof fn lemma_iset_union_finite_iff(s1: ISet, s2: ISet) + ensures + #[trigger] s1.union(s2).finite() <==> s1.finite() && s2.finite(), +{ + if s1.union(s2).finite() { + lemma_iset_union_finite_implies_sets_finite(s1, s2); + } +} + +/// If the union of two `ISet`s is finite, then each of those `ISet`s +/// is also finite. +pub proof fn lemma_iset_union_finite_implies_sets_finite(s1: ISet, s2: ISet) + requires + s1.union(s2).finite(), + ensures + s1.finite(), + s2.finite(), + decreases s1.union(s2).len(), +{ + if s1.union(s2) =~= ISet::::empty() { + assert(s1 =~= ISet::::empty()); + assert(s2 =~= ISet::::empty()); + } else { + let a = s1.union(s2).choose(); + assert(s1.remove(a).union(s2.remove(a)) == s1.union(s2).remove(a)); + lemma_iset_remove_len(s1.union(s2), a); + lemma_iset_union_finite_implies_sets_finite(s1.remove(a), s2.remove(a)); + assert(forall|s: ISet| + #![auto] + s.remove(a).insert(a) == if s.contains(a) { + s + } else { + s.insert(a) + }); + lemma_iset_insert_finite_iff(s1, a); + lemma_iset_insert_finite_iff(s2, a); + } +} + +/// The size of a union of two sets is less than or equal to the size of +/// both individual sets combined. +pub proof fn lemma_len_union(s1: ISet, s2: ISet) + requires + s1.finite(), + s2.finite(), + ensures + s1.union(s2).len() <= s1.len() + s2.len(), + decreases s1.len(), +{ + if s1.is_empty() { + assert(s1.union(s2) =~= s2); + } else { + let a = s1.choose(); + if s2.contains(a) { + assert(s1.union(s2) =~= s1.remove(a).union(s2)); + } else { + assert(s1.union(s2).remove(a) =~= s1.remove(a).union(s2)); + } + lemma_len_union::(s1.remove(a), s2); + } +} + +/// The size of a union of two sets is greater than or equal to the size of +/// both individual sets. +pub proof fn lemma_len_union_ind(s1: ISet, s2: ISet) + requires + s1.finite(), + s2.finite(), + ensures + s1.union(s2).len() >= s1.len(), + s1.union(s2).len() >= s2.len(), + decreases s2.len(), +{ + broadcast use group_iset_properties; + + if s2.len() == 0 { + } else { + let y = choose|y: A| s2.contains(y); + if s1.contains(y) { + assert(s1.remove(y).union(s2.remove(y)) =~= s1.union(s2).remove(y)); + lemma_len_union_ind(s1.remove(y), s2.remove(y)) + } else { + assert(s1.union(s2.remove(y)) =~= s1.union(s2).remove(y)); + lemma_len_union_ind(s1, s2.remove(y)) + } + } +} + +/// The size of the intersection of finite set `s1` and set `s2` is less than or equal to the size of `s1`. +pub proof fn lemma_len_intersect(s1: ISet, s2: ISet) + requires + s1.finite(), + ensures + s1.intersect(s2).len() <= s1.len(), + decreases s1.len(), +{ + if s1.is_empty() { + assert(s1.intersect(s2) =~= s1); + } else { + let a = s1.choose(); + assert(s1.intersect(s2).remove(a) =~= s1.remove(a).intersect(s2)); + lemma_len_intersect::(s1.remove(a), s2); + } +} + +/// If `s1` is a subset of finite set `s2`, then the size of `s1` is less than or equal to +/// the size of `s2` and `s1` must be finite. +pub proof fn lemma_len_subset(s1: ISet, s2: ISet) + requires + s2.finite(), + s1.subset_of(s2), + ensures + s1.len() <= s2.len(), + s1.finite(), +{ + lemma_len_intersect::(s2, s1); + assert(s2.intersect(s1) =~= s1); +} + +/// A subset of a finite set `s` is finite. +pub broadcast proof fn lemma_iset_subset_finite(s: ISet, sub: ISet) + requires + s.finite(), + sub.subset_of(s), + ensures + #![trigger sub.subset_of(s)] + sub.finite(), +{ + let complement = s.difference(sub); + assert(sub =~= s.difference(complement)); +} + +/// The size of the difference of finite set `s1` and set `s2` is less than or equal to the size of `s1`. +pub proof fn lemma_len_difference(s1: ISet, s2: ISet) + requires + s1.finite(), + ensures + s1.difference(s2).len() <= s1.len(), + decreases s1.len(), +{ + if s1.is_empty() { + assert(s1.difference(s2) =~= s1); + } else { + let a = s1.choose(); + assert(s1.difference(s2).remove(a) =~= s1.remove(a).difference(s2)); + lemma_len_difference::(s1.remove(a), s2); + } +} + +/// Creates a finite set of integers in the range [lo, hi). +pub open spec fn set_int_range(lo: int, hi: int) -> ISet { + ISet::new(|i: int| lo <= i && i < hi) +} + +/// If a set solely contains integers in the range [a, b), then its size is +/// bounded by b - a. +pub proof fn lemma_int_range(lo: int, hi: int) + requires + lo <= hi, + ensures + set_int_range(lo, hi).finite(), + set_int_range(lo, hi).len() == hi - lo, + decreases hi - lo, +{ + if lo == hi { + assert(set_int_range(lo, hi) =~= ISet::empty()); + } else { + lemma_int_range(lo, hi - 1); + assert(set_int_range(lo, hi - 1).insert(hi - 1) =~= set_int_range(lo, hi)); + } +} + +/// If x is a subset of y and the size of x is equal to the size of y, x is equal to y. +pub proof fn lemma_subset_equality(x: ISet, y: ISet) + requires + x.subset_of(y), + x.finite(), + y.finite(), + x.len() == y.len(), + ensures + x =~= y, + decreases x.len(), +{ + broadcast use group_iset_properties; + + if x =~= ISet::::empty() { + } else { + let e = x.choose(); + lemma_subset_equality(x.remove(e), y.remove(e)); + } +} + +/// If an injective function is applied to each element of a set to construct +/// another set, the two sets have the same size. +pub proof fn lemma_map_size(x: ISet, y: ISet, f: spec_fn(A) -> B) + requires + x.finite(), + x.injective_on(f), + x.map(f) == y, + ensures + y.finite(), + x.len() == y.len(), + decreases x.len(), +{ + broadcast use group_iset_properties; + + if x.len() == 0 { + if !y.is_empty() { + let e = y.choose(); + } + } else { + let a = x.choose(); + assert(x.remove(a).map(f) == y.remove(f(a))); + lemma_map_size(x.remove(a), y.remove(f(a)), f); + assert(y == y.remove(f(a)).insert(f(a))); + } +} + +/// If any function is applied to each element of a set to construct +/// another set, the constructed set's length is at most the original's +pub proof fn lemma_map_size_bound(x: ISet, y: ISet, f: spec_fn(A) -> B) + requires + x.finite(), + x.map(f) == y, + ensures + y.finite(), + y.len() <= x.len(), + decreases x.len(), +{ + broadcast use group_iset_properties; + + if x.is_empty() { + if !y.is_empty() { + let e = y.choose(); + } + } else { + let xx = x.choose(); + let img = f(xx); + let pre = x.filter(|a: A| f(a) == f(xx)); + x.lemma_len_filter(|a: A| f(a) == f(xx)); + let wit = choose|a: A| x.contains(a) && f(a) == f(xx); + assert forall|b: B| (#[trigger] y.remove(f(xx)).contains(b)) implies exists|a: A| + x.difference(pre).contains(a) && f(a) == b by { + let pre_wit = choose|a: A| x.contains(a) && f(a) == b; + assert(x.difference(pre).contains(pre_wit)); + } + + assert(x == x.difference(pre).union(pre)); + assert(y == y.remove(f(xx)).insert(f(xx))); + assert(x.difference(pre).map(f) == y.remove(f(xx))); + lemma_map_size_bound(x.difference(pre), y.remove(f(xx)), f); + } +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// Taking the union of sets `a` and `b` and then taking the union of the result with `b` +/// is the same as taking the union of `a` and `b` once. +pub broadcast proof fn lemma_iset_union_again1(a: ISet, b: ISet) + ensures + #[trigger] a.union(b).union(b) =~= a.union(b), +{ +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// Taking the union of sets `a` and `b` and then taking the union of the result with `a` +/// is the same as taking the union of `a` and `b` once. +pub broadcast proof fn lemma_iset_union_again2(a: ISet, b: ISet) + ensures + #[trigger] a.union(b).union(a) =~= a.union(b), +{ +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// Taking the intersection of sets `a` and `b` and then taking the intersection of the result with `b` +/// is the same as taking the intersection of `a` and `b` once. +pub broadcast proof fn lemma_iset_intersect_again1(a: ISet, b: ISet) + ensures + #![trigger (a.intersect(b)).intersect(b)] + (a.intersect(b)).intersect(b) =~= a.intersect(b), +{ +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// Taking the intersection of sets `a` and `b` and then taking the intersection of the result with `a` +/// is the same as taking the intersection of `a` and `b` once. +pub broadcast proof fn lemma_iset_intersect_again2(a: ISet, b: ISet) + ensures + #![trigger (a.intersect(b)).intersect(a)] + (a.intersect(b)).intersect(a) =~= a.intersect(b), +{ +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// If set `s2` contains element `a`, then the set difference of `s1` and `s2` does not contain `a`. +pub broadcast proof fn lemma_iset_difference2(s1: ISet, s2: ISet, a: A) + ensures + #![trigger s1.difference(s2).contains(a)] + s2.contains(a) ==> !s1.difference(s2).contains(a), +{ +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// If sets `a` and `b` are disjoint, meaning they have no elements in common, then the set difference +/// of `a + b` and `b` is equal to `a` and the set difference of `a + b` and `a` is equal to `b`. +pub broadcast proof fn lemma_iset_disjoint(a: ISet, b: ISet) + ensures + #![trigger (a + b).difference(a)] //TODO: this might be too free + a.disjoint(b) ==> ((a + b).difference(a) =~= b && (a + b).difference(b) =~= a), +{ +} + +// This verified lemma used to be an axiom in the Dafny prelude +// Dafny encodes the second clause with a single directional, although +// it should be fine with both directions? +// REVIEW: excluded from broadcast group if trigger is too free +// also not that some proofs in seq_lib requires this lemma +/// ISet `s` has length 0 if and only if it is equal to the empty set. If `s` has length greater than 0, +/// Then there must exist an element `x` such that `s` contains `x`. +pub broadcast proof fn lemma_iset_empty_equivalency_len(s: ISet) + requires + s.finite(), + ensures + #![trigger s.len()] + (s.len() == 0 <==> s == ISet::::empty()) && (s.len() != 0 ==> exists|x: A| + s.contains(x)), +{ + assert(s.len() == 0 ==> s =~= ISet::empty()) by { + if s.len() == 0 { + assert(forall|a: A| !(ISet::empty().contains(a))); + assert(ISet::::empty().len() == 0); + assert(ISet::::empty().len() == s.len()); + assert((exists|a: A| s.contains(a)) || (forall|a: A| !s.contains(a))); + if exists|a: A| s.contains(a) { + let a = s.choose(); + assert(s.remove(a).len() == s.len() - 1) by { + lemma_iset_remove_len(s, a); + } + } + } + } + assert(s.len() == 0 <== s =~= ISet::empty()); +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// If sets `a` and `b` are disjoint, meaning they share no elements in common, then the length +/// of the union `a + b` is equal to the sum of the lengths of `a` and `b`. +pub broadcast proof fn lemma_iset_disjoint_lens(a: ISet, b: ISet) + requires + a.finite(), + b.finite(), + ensures + a.disjoint(b) ==> #[trigger] (a + b).len() == a.len() + b.len(), + decreases a.len(), +{ + if a.len() == 0 { + lemma_iset_empty_equivalency_len(a); + assert(a + b =~= b); + } else { + if a.disjoint(b) { + let x = a.choose(); + assert(a.remove(x) + b =~= (a + b).remove(x)); + lemma_iset_disjoint_lens(a.remove(x), b); + } + } +} + +/// Two sets are disjoint iff their intersection is empty +pub proof fn lemma_iset_disjoint_iff_empty_intersection(a: ISet, b: ISet) + ensures + a.disjoint(b) <==> a.intersect(b).is_empty(), +{ + broadcast use group_iset_properties; + + if a.disjoint(b) { + assert(b.disjoint(a)); + assert(forall|x: T| a.contains(x) ==> !(a.contains(x) && b.contains(x))); + assert(forall|x: T| b.contains(x) ==> !(a.contains(x) && b.contains(x))); + assert(forall|x: T| !a.intersect(b).contains(x)); + } + if a.intersect(b).is_empty() { + assert(forall|x: T| !a.intersect(b).contains(x)); + if !a.disjoint(b) { + assert(exists|x: T| a.contains(x) && b.contains(x)); + let x = choose|x: T| a.contains(x) && b.contains(x); + assert(a.intersect(b).contains(x)); + assert(!a.intersect(b).is_empty()); + } + } +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// The length of the union between two sets added to the length of the intersection between the +/// two sets is equal to the sum of the lengths of the two sets. +pub broadcast proof fn lemma_iset_intersect_union_lens(a: ISet, b: ISet) + requires + a.finite(), + b.finite(), + ensures + #[trigger] (a + b).len() + #[trigger] a.intersect(b).len() == a.len() + b.len(), + decreases a.len(), +{ + if a.len() == 0 { + lemma_iset_empty_equivalency_len(a); + assert(a + b =~= b); + assert(a.intersect(b) =~= ISet::empty()); + assert(a.intersect(b).len() == 0); + } else { + let x = a.choose(); + lemma_iset_intersect_union_lens(a.remove(x), b); + if (b.contains(x)) { + assert(a.remove(x) + b =~= (a + b)); + assert(a.intersect(b).remove(x) =~= a.remove(x).intersect(b)); + } else { + assert(a.remove(x) + b =~= (a + b).remove(x)); + assert(a.remove(x).intersect(b) =~= a.intersect(b)); + } + } +} + +// This verified lemma used to be an axiom in the Dafny prelude +/// The length of the set difference `A \ B` added to the length of the set difference `B \ A` added to +/// the length of the intersection `A ∩ B` is equal to the length of the union `A + B`. +/// +/// The length of the set difference `A \ B` is equal to the length of `A` minus the length of the +/// intersection `A ∩ B`. +pub broadcast proof fn lemma_iset_difference_len(a: ISet, b: ISet) + requires + a.finite(), + b.finite(), + ensures + (#[trigger] a.difference(b).len() + b.difference(a).len() + a.intersect(b).len() == (a + + b).len()) && (a.difference(b).len() == a.len() - a.intersect(b).len()), + decreases a.len(), +{ + if a.len() == 0 { + lemma_iset_empty_equivalency_len(a); + assert(a.difference(b) =~= ISet::empty()); + assert(b.difference(a) =~= b); + assert(a.intersect(b) =~= ISet::empty()); + assert(a + b =~= b); + } else { + let x = a.choose(); + lemma_iset_difference_len(a.remove(x), b); + if b.contains(x) { + assert(a.intersect(b).remove(x) =~= a.remove(x).intersect(b)); + assert(a.remove(x).difference(b) =~= a.difference(b)); + assert(b.difference(a.remove(x)).remove(x) =~= b.difference(a)); + assert(a.remove(x) + b =~= a + b); + } else { + assert(a.remove(x) + b =~= (a + b).remove(x)); + assert(a.remove(x).difference(b) =~= a.difference(b).remove(x)); + assert(b.difference(a.remove(x)) =~= b.difference(a)); + assert(a.remove(x).intersect(b) =~= a.intersect(b)); + } + } +} + +pub broadcast group group_iset_properties { + lemma_iset_union_again1, + lemma_iset_union_again2, + lemma_iset_intersect_again1, + lemma_iset_intersect_again2, + lemma_iset_difference2, + lemma_iset_disjoint, + lemma_iset_disjoint_lens, + lemma_iset_intersect_union_lens, + lemma_iset_difference_len, + // REVIEW: exclude from broadcast group if trigger is too free + // also note that some proofs in seq_lib requires this lemma + lemma_iset_empty_equivalency_len, +} + +pub broadcast axiom fn axiom_iset_is_empty(s: ISet) + requires + !(#[trigger] s.is_empty()), + ensures + exists|a: A| + s.contains( + a, + ), +// REVIEW, should this be in `set`, or have a proof? + +; + +pub broadcast proof fn lemma_iset_is_empty_len0(s: ISet) + ensures + #[trigger] s.is_empty() <==> (s.finite() && s.len() == 0), +{ +} + +#[doc(hidden)] +#[verifier::inline] +pub open spec fn check_argument_is_set(s: ISet) -> ISet { + s +} + +/// Prove two sets equal by extensionality. Usage is: +/// +/// ```rust +/// assert_isets_equal!(set1 == set2); +/// ``` +/// +/// or, +/// +/// ```rust +/// assert_isets_equal!(set1 == set2, elem => { +/// // prove that set1.contains(elem) iff set2.contains(elem) +/// }); +/// ``` +#[macro_export] +macro_rules! assert_isets_equal { + [$($tail:tt)*] => { + $crate::vstd::prelude::verus_proof_macro_exprs!($crate::vstd::iset_lib::assert_isets_equal_internal!($($tail)*)) + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! assert_isets_equal_internal { + (::vstd::prelude::spec_eq($s1:expr, $s2:expr)) => { + $crate::vstd::iset_lib::assert_isets_equal_internal!($s1, $s2) + }; + (::vstd::prelude::spec_eq($s1:expr, $s2:expr), $elem:ident $( : $t:ty )? => $bblock:block) => { + $crate::vstd::iset_lib::assert_isets_equal_internal!($s1, $s2, $elem $( : $t )? => $bblock) + }; + (crate::prelude::spec_eq($s1:expr, $s2:expr)) => { + $crate::vstd::iset_lib::assert_isets_equal_internal!($s1, $s2) + }; + (crate::prelude::spec_eq($s1:expr, $s2:expr), $elem:ident $( : $t:ty )? => $bblock:block) => { + $crate::vstd::iset_lib::assert_isets_equal_internal!($s1, $s2, $elem $( : $t )? => $bblock) + }; + (crate::verus_builtin::spec_eq($s1:expr, $s2:expr)) => { + $crate::vstd::iset_lib::assert_isets_equal_internal!($s1, $s2) + }; + (crate::verus_builtin::spec_eq($s1:expr, $s2:expr), $elem:ident $( : $t:ty )? => $bblock:block) => { + $crate::vstd::iset_lib::assert_isets_equal_internal!($s1, $s2, $elem $( : $t )? => $bblock) + }; + ($s1:expr, $s2:expr $(,)?) => { + $crate::vstd::iset_lib::assert_isets_equal_internal!($s1, $s2, elem => { }) + }; + ($s1:expr, $s2:expr, $elem:ident $( : $t:ty )? => $bblock:block) => { + #[verifier::spec] let s1 = $crate::vstd::iset_lib::check_argument_is_set($s1); + #[verifier::spec] let s2 = $crate::vstd::iset_lib::check_argument_is_set($s2); + $crate::vstd::prelude::assert_by($crate::vstd::prelude::equal(s1, s2), { + $crate::vstd::prelude::assert_forall_by(|$elem $( : $t )?| { + $crate::vstd::prelude::ensures( + $crate::vstd::prelude::imply(s1.contains($elem), s2.contains($elem)) + && + $crate::vstd::prelude::imply(s2.contains($elem), s1.contains($elem)) + ); + { $bblock } + }); + $crate::vstd::prelude::assert_($crate::vstd::prelude::ext_equal(s1, s2)); + }); + } +} + +pub broadcast group group_iset_lib_default { + axiom_iset_is_empty, + lemma_iset_is_empty_len0, + lemma_iset_subset_finite, + ISet::lemma_map_by_finite, + ISet::lemma_map_flatten_by_finite, +} + +pub use assert_isets_equal_internal; +pub use assert_isets_equal; + +} // verus! diff --git a/source/vstd/logatom.rs b/source/vstd/logatom.rs index 692f8da818..4906a2798c 100644 --- a/source/vstd/logatom.rs +++ b/source/vstd/logatom.rs @@ -60,8 +60,8 @@ pub trait MutOperation: Sized { pub trait ReadLinearizer: Sized { type Completion; - open spec fn namespaces(self) -> Set { - Set::empty() + open spec fn namespaces(self) -> ISet { + ISet::empty() } open spec fn pre(self, op: Op) -> bool { @@ -99,8 +99,8 @@ pub trait ReadLinearizer: Sized { pub trait MutLinearizer: Sized { type Completion; - open spec fn namespaces(self) -> Set { - Set::empty() + open spec fn namespaces(self) -> ISet { + ISet::empty() } open spec fn pre(self, op: Op) -> bool { diff --git a/source/vstd/map.rs b/source/vstd/map.rs index 39c247844a..67a35261b1 100644 --- a/source/vstd/map.rs +++ b/source/vstd/map.rs @@ -8,7 +8,7 @@ use verus as verus_; // skip verusfmt due to unhandled return-value-pattern verus_! { /// `Map` is an abstract map type for specifications. -/// To use a "map" in compiled code, use an `exec` type like HashMap (TODO) +/// To use a "map" in compiled code, use an `exec` type like HashMap /// that has a `Map` as its specification type. /// /// An object `map: Map` has a _domain_, a set of keys given by [`map.dom()`](Map::dom), @@ -16,54 +16,52 @@ verus_! { /// Alternatively, a map can be thought of as a set of `(K, V)` pairs where each key /// appears in at most entry. /// -/// In general, a map might be infinite. -/// To work specifically with finite maps, see the [`self.finite()`](Set::finite) predicate. +/// A Map must will always be finite. +/// To work with infinite maps, see `IMap`. /// /// Maps can be constructed in a few different ways: /// * [`Map::empty()`] constructs an empty map. -/// * [`Map::new`] and [`Map::total`] construct a map given functions that specify its domain and the mapping +/// * [`Map::new`] constructs a map given a `Set` that specifies its domain and the mapping /// from keys to values (a _map comprehension_). /// * The [`map!`] macro, to construct small maps of a fixed size. /// * By manipulating an existing map with [`Map::insert`] or [`Map::remove`]. /// /// To prove that two maps are equal, it is usually easiest to use the extensionality operator `=~=`. #[verifier::ext_equal] -#[verifier::reject_recursive_types(K)] +#[verifier::accept_recursive_types(K)] #[verifier::accept_recursive_types(V)] pub tracked struct Map { - mapping: spec_fn(K) -> Option, + dummy_key: core::marker::PhantomData, + dummy_value: core::marker::PhantomData, } impl Map { - /// An empty map. - pub closed spec fn empty() -> Map { - Map { mapping: |k| None } - } - - /// Gives a `Map` whose domain contains every key, and maps each key - /// to the value given by `fv`. - pub open spec fn total(fv: spec_fn(K) -> V) -> Map { - Set::full().mk_map(fv) - } - - /// Gives a `Map` whose domain is given by the boolean predicate on keys `fk`, - /// and maps each key to the value given by `fv`. - pub open spec fn new(fk: spec_fn(K) -> bool, fv: spec_fn(K) -> V) -> Map { - Set::new(fk).mk_map(fv) - } - /// The domain of the map as a set. - pub closed spec fn dom(self) -> Set { - Set::new(|k| (self.mapping)(k) is Some) - } + pub uninterp spec fn dom(self) -> Set; /// Gets the value that the given key `key` maps to. /// For keys not in the domain, the result is meaningless and arbitrary. - pub closed spec fn index(self, key: K) -> V + pub uninterp spec fn index(self, key: K) -> V recommends self.dom().contains(key), - { - (self.mapping)(key)->Some_0 + ; + + /// Gives a `Map` whose domain is given, mapping each key to + /// the value given by `fv`. + pub uninterp spec fn new(s: Set, fv: spec_fn(K) -> V) -> Map; + + /// This axiom says that `Map::new` produces a `Map` whose domain + /// is given, mapping each key `k` to the value given by `fv(k)`. + broadcast axiom fn axiom_new(s: Set, fv: spec_fn(K) -> V) + ensures + #![trigger Self::new(s, fv)] + Self::new(s, fv).dom() == s, + forall|k| s.contains(k) ==> #[trigger] Self::new(s, fv)[k] == fv(k), + ; + + /// An empty map. + pub closed spec fn empty() -> Map { + Self::new(Set::::empty(), |k| arbitrary()) } /// `[]` operator, synonymous with `index` @@ -80,28 +78,14 @@ impl Map { /// If the key is already present from the map, then its existing value is overwritten /// by the new value. pub closed spec fn insert(self, key: K, value: V) -> Map { - Map { - mapping: |k| - if k == key { - Some(value) - } else { - (self.mapping)(k) - }, - } + Map::new(self.dom().insert(key), |k| if k == key { value } else { self[k] }) } /// Removes the given key and its associated value from the map. /// /// If the key is already absent from the map, then the map is left unchanged. pub closed spec fn remove(self, key: K) -> Map { - Map { - mapping: |k| - if k == key { - None - } else { - (self.mapping)(k) - }, - } + Map::new(self.dom().remove(key), |k| self[k]) } /// Returns the number of key-value pairs in the map @@ -109,6 +93,17 @@ impl Map { self.dom().len() } + /// Converts this `Map` into an equivalent `IMap` + pub open spec fn to_imap(self) -> IMap { + IMap::new(|k| self.dom().contains(k), |k| self[k]) + } + + /// Indicates if this `Map` is equivalent to the given `IMap`. + pub open spec fn congruent(self, m2: IMap) -> bool { + &&& self.dom().congruent(m2.dom()) + &&& forall|k| #[trigger] self.dom().contains(k) ==> self[k] == m2[k] + } + /// Create an empty tracked map. /// /// This allows us to create a map, which we know is empty, that is _tracked_. @@ -210,119 +205,107 @@ impl Map { } // Trusted axioms -/* REVIEW: this is simpler than the two separate axioms below -- would this be ok? pub broadcast axiom fn axiom_map_index_decreases(m: Map, key: K) requires m.dom().contains(key), ensures #[trigger](decreases_to!(m => m[key])); -*/ -pub broadcast axiom fn axiom_map_index_decreases_finite(m: Map, key: K) - requires - m.dom().finite(), - m.dom().contains(key), +/// Since `Map::new` is uninterpret, this broadcast lemma is needed to establish +/// that it produces a map with the given set as its domain. +pub broadcast proof fn lemma_map_new_domain(s: Set, fv: spec_fn(K) -> V) ensures - #[trigger] (decreases_to!(m => m[key])), -; + #![trigger Map::new(s, fv)] + Map::new(s, fv).dom() == s, +{ + broadcast use Map::axiom_new; +} -// REVIEW: this is currently a special case that is hard-wired into the verifier -// It implements a version of https://github.com/FStarLang/FStar/pull/2954 . -pub broadcast axiom fn axiom_map_index_decreases_infinite(m: Map, key: K) +/// Since `Map::new` is closed, this broadcast lemma is needed to establish +/// that it produces a map that maps elements using the given function. +pub broadcast proof fn lemma_map_new_index(s: Set, fv: spec_fn(K) -> V, k: K) requires - m.dom().contains(key), + s.contains(k), ensures - #[trigger] is_smaller_than_recursive_function_field(m[key], m), -; + #![trigger Map::new(s, fv)[k]] + Map::new(s, fv)[k] == fv(k) +{ + broadcast use Map::axiom_new; +} /// The domain of the empty map is the empty set -pub broadcast proof fn axiom_map_empty() +pub broadcast proof fn lemma_map_empty() ensures #[trigger] Map::::empty().dom() == Set::::empty(), { - broadcast use super::set::group_set_axioms; - - assert(Set::new(|k: K| (|k| None::)(k) is Some) == Set::::empty()); + broadcast use Map::axiom_new; } /// The domain of a map after inserting a key-value pair is equivalent to inserting the key into /// the original map's domain set. -pub broadcast proof fn axiom_map_insert_domain(m: Map, key: K, value: V) +pub broadcast proof fn lemma_map_insert_domain(m: Map, key: K, value: V) ensures #[trigger] m.insert(key, value).dom() == m.dom().insert(key), { - broadcast use super::set::group_set_axioms; - - assert(m.insert(key, value).dom() =~= m.dom().insert(key)); + broadcast use Map::axiom_new; + broadcast use super::set::group_set_lemmas; + broadcast use super::set_lib::group_set_lib_default; } /// Inserting `value` at `key` in `m` results in a map that maps `key` to `value` -pub broadcast proof fn axiom_map_insert_same(m: Map, key: K, value: V) +pub broadcast proof fn lemma_map_insert_same(m: Map, key: K, value: V) ensures #[trigger] m.insert(key, value)[key] == value, { + broadcast use Map::axiom_new; + broadcast use super::set::group_set_lemmas; + broadcast use super::set_lib::group_set_lib_default; } /// Inserting `value` at `key2` does not change the value mapped to by any other keys in `m` -pub broadcast proof fn axiom_map_insert_different(m: Map, key1: K, key2: K, value: V) +pub broadcast proof fn lemma_map_insert_different(m: Map, key1: K, key2: K, value: V) requires key1 != key2, ensures #[trigger] m.insert(key2, value)[key1] == m[key1], { + broadcast use Map::axiom_new; } /// The domain of a map after removing a key-value pair is equivalent to removing the key from /// the original map's domain set. -pub broadcast proof fn axiom_map_remove_domain(m: Map, key: K) +pub broadcast proof fn lemma_map_remove_domain(m: Map, key: K) ensures #[trigger] m.remove(key).dom() == m.dom().remove(key), { - broadcast use super::set::group_set_axioms; - - assert(m.remove(key).dom() =~= m.dom().remove(key)); + broadcast use Map::axiom_new; + broadcast use super::set::group_set_lemmas; + broadcast use super::set_lib::group_set_lib_default; } /// Removing a key-value pair from a map does not change the value mapped to by /// any other keys in the map. -pub broadcast proof fn axiom_map_remove_different(m: Map, key1: K, key2: K) +pub broadcast proof fn lemma_map_remove_different(m: Map, key1: K, key2: K) requires key1 != key2, ensures #[trigger] m.remove(key2)[key1] == m[key1], { + broadcast use Map::axiom_new; } /// Two maps are equivalent if their domains are equivalent and every key in their domains map to the same value. -pub broadcast proof fn axiom_map_ext_equal(m1: Map, m2: Map) +pub broadcast axiom fn axiom_map_ext_equal(m1: Map, m2: Map) ensures #[trigger] (m1 =~= m2) <==> { &&& m1.dom() =~= m2.dom() &&& forall|k: K| #![auto] m1.dom().contains(k) ==> m1[k] == m2[k] }, -{ - broadcast use super::set::group_set_axioms; - - if m1 =~= m2 { - assert(m1.dom() =~= m2.dom()); - assert(forall|k: K| #![auto] m1.dom().contains(k) ==> m1[k] == m2[k]); - } - if ({ - &&& m1.dom() =~= m2.dom() - &&& forall|k: K| #![auto] m1.dom().contains(k) ==> m1[k] == m2[k] - }) { - if m1.mapping != m2.mapping { - assert(exists|k| #[trigger] (m1.mapping)(k) != (m2.mapping)(k)); - let k = choose|k| #[trigger] (m1.mapping)(k) != (m2.mapping)(k); - if m1.dom().contains(k) { - assert(m1[k] == m2[k]); - } - assert(false); - } - assert(m1 =~= m2); - } -} +; +/// Two maps are deeply equivalent if their domains are equivalent and +/// every key in their domains map to values that are deeply +/// equivalent. pub broadcast proof fn axiom_map_ext_equal_deep(m1: Map, m2: Map) ensures #[trigger] (m1 =~~= m2) <==> { @@ -333,15 +316,16 @@ pub broadcast proof fn axiom_map_ext_equal_deep(m1: Map, m2: Map Map { /// Is `true` if called by a "full" map, i.e., a map containing every element of type `A`. #[verifier::inline] pub open spec fn is_full(self) -> bool { - self.dom().is_full() + forall|k: K| self.dom().contains(k) } /// Is `true` if called by an "empty" map, i.e., a map containing no elements and has length 0 @@ -62,7 +67,7 @@ impl Map { /// } /// ``` pub open spec fn values(self) -> Set { - Set::::new(|v: V| self.contains_value(v)) + self.dom().map(|k: K| self[k]) } /// @@ -79,7 +84,7 @@ impl Map { /// } /// ``` pub open spec fn kv_pairs(self) -> Set<(K, V)> { - Set::<(K, V)>::new(|kv: (K, V)| self.dom().contains(kv.0) && self[kv.0] == kv.1) + self.dom().map(|k: K| (k, self[k])) } /// Returns true if the key `k` is in the domain of `self`, and it maps to the value `v`. @@ -122,7 +127,7 @@ impl Map { /// ``` pub open spec fn union_prefer_right(self, m2: Self) -> Self { Self::new( - |k: K| self.dom().contains(k) || m2.dom().contains(k), + self.dom().union(m2.dom()), |k: K| if m2.dom().contains(k) { m2[k] @@ -144,7 +149,7 @@ impl Map { /// =~= map![1 => 10]); /// ``` pub open spec fn remove_keys(self, keys: Set) -> Self { - Self::new(|k: K| self.dom().contains(k) && !keys.contains(k), |k: K| self[k]) + Self::new(self.dom().difference(keys), |k: K| self[k]) } /// Complement to `remove_keys`. Restricts the map to (key, value) pairs @@ -159,7 +164,7 @@ impl Map { /// =~= map![2 => 11, 3 => 12]); /// ``` pub open spec fn restrict(self, keys: Set) -> Self { - Self::new(|k: K| self.dom().contains(k) && keys.contains(k), |k: K| self[k]) + Self::new(self.dom().intersect(keys), |k: K| self[k]) } /// Returns `true` if and only if the given key maps to the same value or does not exist in self and m2. @@ -175,12 +180,12 @@ impl Map { /// Map a function `f` over all (k, v) pairs in `self`. pub open spec fn map_entries(self, f: spec_fn(K, V) -> W) -> Map { - Map::new(|k: K| self.contains_key(k), |k: K| f(k, self[k])) + Map::::new(self.dom(), |k: K| f(k, self[k])) } /// Map a function `f` over the values in `self`. pub open spec fn map_values(self, f: spec_fn(V) -> W) -> Map { - Map::new(|k: K| self.contains_key(k), |k: K| f(self[k])) + Map::::new(self.dom(), |k: K| f(self[k])) } /// Returns `true` if and only if a map is injective @@ -193,10 +198,7 @@ impl Map { /// Swaps map keys and values. Values are not required to be unique; no /// promises on which key is chosen on the intersection. pub open spec fn invert(self) -> Map { - Map::::new( - |v: V| self.contains_value(v), - |v: V| choose|k: K| self.contains_pair(k, v), - ) + Map::::new(self.values(), |v: V| choose|k: K| self.contains_pair(k, v)) } // Proven lemmas @@ -205,7 +207,6 @@ impl Map { pub proof fn lemma_remove_key_len(self, key: K) requires self.dom().contains(key), - self.dom().finite(), ensures self.dom().len() == 1 + self.remove(key).dom().len(), { @@ -224,8 +225,6 @@ impl Map { pub proof fn lemma_remove_keys_len(self, keys: Set) requires forall|k: K| #[trigger] keys.contains(k) ==> self.contains_key(k), - keys.finite(), - self.dom().finite(), ensures self.remove_keys(keys).dom().len() == self.dom().len() - keys.len(), decreases keys.len(), @@ -251,9 +250,11 @@ impl Map { x != y && self.invert().dom().contains(x) && self.invert().dom().contains( y, ) implies #[trigger] self.invert()[x] != #[trigger] self.invert()[y] by { + assert(exists|i: K| #[trigger] self.dom().contains(i) && self[i] == x); let i = choose|i: K| #[trigger] self.dom().contains(i) && self[i] == x; assert(self.contains_pair(i, x)); let j = choose|j: K| self.contains_pair(j, x) && self.invert()[x] == j; + assert(exists|k: K| #[trigger] self.dom().contains(k) && self[k] == y); let k = choose|k: K| #[trigger] self.dom().contains(k) && self[k] == y; assert(self.contains_pair(k, y)); let l = choose|l: K| self.contains_pair(l, y) && self.invert()[y] == l && l != j; @@ -458,17 +459,15 @@ impl Map { pub proof fn lemma_injective_values_len(self) requires - self.dom().finite(), self.is_injective(), ensures - self.values().finite(), self.values().len() == self.dom().len(), { let f = |k: K| if self.contains_key(k) { self[k] } else { - Set::::full().difference(self.values()).choose() + arbitrary() }; assert(forall|a1: K, a2: K| self.dom().contains(a1) && self.dom().contains(a2) && #[trigger] f(a1) == #[trigger] f( @@ -479,17 +478,14 @@ impl Map { } pub proof fn lemma_values_len(self) - requires - self.dom().finite(), ensures - self.values().finite(), self.values().len() <= self.dom().len(), { let f = |k: K| if self.contains_key(k) { self[k] } else { - Set::::full().difference(self.values()).choose() + Set::::full().unwrap().difference(self.values()).choose() }; assert(self.dom().map(f) == self.values()); lemma_map_size_bound(self.dom(), self.values(), f); @@ -516,7 +512,62 @@ impl Map, V> { /// } /// ``` pub open spec fn prefixed_entries(self, prefix: Seq) -> Self { - Map::new(|k: Seq| self.contains_key(prefix + k), |k: Seq| self[prefix + k]) + Map::new( + Set::new(|k: Seq| self.contains_key(prefix + k)).unwrap(), + |k: Seq| self[prefix + k], + ) + } + + /// This internal helper lemma says that if sequences `s1` and + /// `s2` differ, then they differ when prefixed by the sequence + /// `prefix`. + proof fn lemma_ne_implies_prefixed_ne(prefix: Seq, s1: Seq, s2: Seq) + requires + s1 != s2, + ensures + prefix + s1 != prefix + s2, + decreases prefix.len(), + { + broadcast use super::seq::group_seq_axioms; + + if s1.len() == s2.len() { + if forall|i: int| 0 <= i < s1.len() ==> s1[i] == s2[i] { + assert(s1 =~= s2); + } else { + let i: int = choose|i: int| 0 <= i < s1.len() && s1[i] != s2[i]; + assert((prefix + s1)[prefix.len() + i] == s1[i]); + assert((prefix + s2)[prefix.len() + i] == s2[i]); + } + } else { + assert((prefix + s1).len() == prefix.len() + s1.len()); + assert((prefix + s2).len() == prefix.len() + s2.len()); + } + } + + /// This helper lemma establishes that the set used in the body of + /// `prefixed_entries` is finite, so the set it produces is valid. + proof fn lemma_prefixed_entries_dom_finite(self, prefix: Seq) + ensures + ISet::new(|k: Seq| self.contains_key(prefix + k)).finite(), + decreases self.dom().len(), + { + let f = |k: Seq| self.contains_key(prefix + k); + if forall|k: Seq| !(#[trigger] self.contains_key(prefix + k)) { + assert(ISet::new(f) =~= ISet::empty()); + } else { + let s: Seq = choose|s: Seq| #[trigger] self.contains_key(prefix + s); + self.remove(prefix + s).lemma_prefixed_entries_dom_finite(prefix); + let set_remove_f = |k: Seq| self.remove(prefix + s).contains_key(prefix + k); + let is1 = ISet::new(f); + let is2 = ISet::new(set_remove_f).insert(s); + assert forall|x| is1.contains(x) implies is2.contains(x) by { + if x != s { + Self::lemma_ne_implies_prefixed_ne(prefix, s, x); + assert(self.remove(prefix + s).contains_key(prefix + x)); + } + } + assert(ISet::new(f) =~= ISet::new(set_remove_f).insert(s)); + } } /// For every key `k` kept by `prefixed_entries(prefix)`, @@ -541,6 +592,7 @@ impl Map, V> { { broadcast use group_map_properties; + self.lemma_prefixed_entries_dom_finite(prefix); } /// A key `k` is in `prefixed_entries(prefix)` exactly when the original map @@ -571,6 +623,8 @@ impl Map, V> { let lhs = self.prefixed_entries(prefix); let rhs = self; + + self.lemma_prefixed_entries_dom_finite(prefix); } /// Inserting `(prefix + k, v)` before taking `prefixed_entries(prefix)` @@ -708,13 +762,11 @@ pub broadcast proof fn lemma_union_dom(m1: Map, m2: Map) pub broadcast proof fn lemma_disjoint_union_size(m1: Map, m2: Map) requires m1.dom().disjoint(m2.dom()), - m1.dom().finite(), - m2.dom().finite(), ensures #[trigger] m1.union_prefer_right(m2).dom().len() == m1.dom().len() + m2.dom().len(), { let u = m1.union_prefer_right(m2); - assert(u.dom() =~= m1.dom() + m2.dom()); //proves u.dom() is finite + assert(u.dom() =~= m1.dom() + m2.dom()); assert(u.remove_keys(m1.dom()).dom() =~= m2.dom()); assert(u.remove_keys(m1.dom()).dom().len() == u.dom().len() - m1.dom().len()) by { u.lemma_remove_keys_len(m1.dom()); @@ -744,33 +796,22 @@ pub broadcast proof fn lemma_submap_of_trans(m1: Map, m2: Map, } } -// This verified lemma used to be an axiom in the Dafny prelude +// This verified lemma corresponds to an axiom in the Dafny prelude saying that: /// The domain of a map constructed with `Map::new(fk, fv)` is equivalent to the set constructed with `Set::new(fk)`. -pub broadcast proof fn lemma_map_new_domain(fk: spec_fn(K) -> bool, fv: spec_fn(K) -> V) +pub broadcast proof fn lemma_map_new_domain(s: Set, fv: spec_fn(K) -> V) ensures - #[trigger] Map::::new(fk, fv).dom() == Set::::new(|k: K| fk(k)), + #[trigger] Map::::new(s, fv).dom() == s, { - assert(Set::new(fk) =~= Set::::new(|k: K| fk(k))); } -// This verified lemma used to be an axiom in the Dafny prelude +// This verified lemma used to be an axiom in the Dafny prelude saying that: /// The set of values of a map constructed with `Map::new(fk, fv)` is equivalent to /// the set constructed with `Set::new(|v: V| (exists |k: K| fk(k) && fv(k) == v)`. In other words, /// the set of all values fv(k) where fk(k) is true. -pub broadcast proof fn lemma_map_new_values(fk: spec_fn(K) -> bool, fv: spec_fn(K) -> V) +pub broadcast proof fn lemma_map_new_values(s: Set, fv: spec_fn(K) -> V) ensures - #[trigger] Map::::new(fk, fv).values() == Set::::new( - |v: V| (exists|k: K| #[trigger] fk(k) && #[trigger] fv(k) == v), - ), + #[trigger] Map::::new(s, fv).values() == s.map(fv), { - let keys = Set::::new(fk); - let values = Map::::new(fk, fv).values(); - let map = Map::::new(fk, fv); - assert(map.dom() =~= keys); - assert(forall|k: K| #[trigger] fk(k) ==> keys.contains(k)); - assert(values =~= Set::::new( - |v: V| (exists|k: K| #[trigger] fk(k) && #[trigger] fv(k) == v), - )); } pub broadcast group group_map_properties { @@ -789,38 +830,4 @@ pub broadcast group group_map_extra { Map::lemma_prefixed_entries_union, } -pub proof fn lemma_values_finite(m: Map) - requires - m.dom().finite(), - ensures - m.values().finite(), - decreases m.len(), -{ - if m.len() > 0 { - let k = m.dom().choose(); - let v = m[k]; - let m1 = m.remove(k); - assert(m.contains_key(k)); - assert(m.contains_value(v)); - let mv = m.values(); - let m1v = m1.values(); - assert_sets_equal!(mv == m1v.insert(v), v0 => { - if m.contains_value(v0) { - if v0 != v { - let k0 = choose|k0| #![auto] m.contains_key(k0) && m[k0] == v0; - assert(k0 != k); - assert(m1.contains_key(k0)); - assert(mv.contains(v0) ==> m1v.insert(v).contains(v0)); - assert(mv.contains(v0) <== m1v.insert(v).contains(v0)); - } - } - }); - assert(m1.len() < m.len()); - lemma_values_finite(m1); - axiom_set_insert_finite(m1.values(), v); - } else { - assert(m.values() =~= Set::::empty()); - } -} - } // verus! diff --git a/source/vstd/multiset.rs b/source/vstd/multiset.rs index 868a4b30f0..1e916a6348 100644 --- a/source/vstd/multiset.rs +++ b/source/vstd/multiset.rs @@ -15,6 +15,8 @@ use super::set::*; verus! { +broadcast use group_set_lemmas; + /// `Multiset` is an abstract multiset type for specifications. /// /// `Multiset` can be encoded as a (total) map from elements to natural numbers, @@ -30,16 +32,23 @@ verus! { /// /// To prove that two multisets are equal, it is usually easiest to use the /// extensionality operator `=~=`. +/// // We could in principle implement the Multiset via an inductive datatype // and so we can mark its type argument as accept_recursive_types. -// Note: Multiset is finite (in contrast to Set, Map, which are infinite) because it -// isn't entirely obvious how to represent an infinite multiset in the case where -// a single value (v: V) has an infinite multiplicity. It seems to require either: +// Note: Multiset is finite because it isn't entirely obvious how to represent an +// infinite multiset in the case where a single value (v: V) has an infinite +// multiplicity. It seems to require either: // (1) representing multiplicity by an ordinal or cardinal or something // (2) limiting each multiplicity to be finite // (1) would be complicated and it's not clear what the use would be; (2) has some // weird properties (e.g., you can't in general define a multiset `map` function // since it might map an infinite number of elements to the same one). +// Also, note that if multiset were infinite, it couldn't accept_recursive_types. +// +// Perhaps someday Verus will be able to mark finite-Map as accept_recursive_types, then this file +// can be rewritten as just a wrapper around Map rather than the present pile of trusted axioms. +// See https://github.com/verus-lang/verus/discussions/1663 +// #[verifier::external_body] #[verifier::ext_equal] #[verifier::accept_recursive_types(V)] @@ -58,12 +67,11 @@ impl Multiset { pub uninterp spec fn empty() -> Self; /// Creates a multiset whose elements are given by the domain of the map `m` and whose - /// multiplicities are given by the corresponding values of `m[element]`. The map `m` - /// must be finite, or else this multiset is arbitrary. + /// multiplicities are given by the corresponding values of `m[element]`. pub uninterp spec fn from_map(m: Map) -> Self; pub open spec fn from_set(m: Set) -> Self { - Self::from_map(Map::new(|k| m.contains(k), |v| 1)) + Self::from_map(Map::new(m, |v| 1)) } /// A singleton multiset, i.e., a multiset with a single element of multiplicity 1. @@ -99,7 +107,7 @@ impl Multiset { /// Updates the multiplicity of the value `v` in the multiset to `mult`. pub open spec fn update(self, v: V, mult: nat) -> Self { let map = Map::new( - |key: V| (self.contains(key) || key == v), + self.dom().insert(v), |key: V| if key == v { mult @@ -151,7 +159,7 @@ impl Multiset { /// the elements that "overlap". pub open spec fn intersection_with(self, other: Self) -> Self { let m = Map::::new( - |v: V| self.contains(v), + self.dom(), |v: V| min(self.count(v) as int, other.count(v) as int) as nat, ); Self::from_map(m) @@ -160,10 +168,7 @@ impl Multiset { /// Returns a multiset containing the difference between the count of a /// given element of the two sets. pub open spec fn difference_with(self, other: Self) -> Self { - let m = Map::::new( - |v: V| self.contains(v), - |v: V| clip(self.count(v) - other.count(v)), - ); + let m = Map::::new(self.dom(), |v: V| clip(self.count(v) - other.count(v))); Self::from_map(m) } @@ -174,9 +179,30 @@ impl Multiset { forall|x: V| self.count(x) == 0 || other.count(x) == 0 } + // This module assumes that all well-formed multisets have finite footprint, + // so this axiom is reasonable. + axiom fn axiom_dom_finite(self) + ensures + #[trigger] ISet::new(|v: V| self.count(v) > 0).finite(), + ; + /// Returns the set of all elements that have a count greater than 0 - pub open spec fn dom(self) -> Set { - Set::new(|v: V| self.count(v) > 0) + pub closed spec fn dom(self) -> Set { + Set::new(|v: V| self.count(v) > 0).unwrap() + } + + // dom() won't mean anything unless we know our domain is finite, which is a soundness + // invariant that the present axiom-heavy representation promises to maintain. + pub broadcast proof fn dom_ensures(self) + ensures + #![trigger(self.dom())] + forall|v: V| #[trigger] + self.dom().contains(v) <==> self.count(v) > 0, + { + self.axiom_dom_finite(); + assert forall|v: V| #[trigger] self.dom().contains(v) <==> self.count(v) > 0 by { + super::iset::lemma_iset_new(|vv: V| self.count(vv) > 0, v); + } } } @@ -206,9 +232,10 @@ pub broadcast proof fn lemma_multiset_empty_len(m: Multiset) /// value `v` to multiplicity `m[v]` if `v` is in the domain of `m`. pub broadcast axiom fn axiom_multiset_contained(m: Map, v: V) requires - m.dom().finite(), m.dom().contains(v), ensures +// #[trigger] Multiset::from_map(m).dom().contains(v), + #[trigger] Multiset::from_map(m).count(v) == m[v], ; @@ -216,12 +243,46 @@ pub broadcast axiom fn axiom_multiset_contained(m: Map, v: V) /// value `v` to multiplicity 0 if `v` is not in the domain of `m`. pub broadcast axiom fn axiom_multiset_new_not_contained(m: Map, v: V) requires - m.dom().finite(), !m.dom().contains(v), ensures #[trigger] Multiset::from_map(m).count(v) == 0, ; +pub broadcast proof fn lemma_from_map_dom(mymap: Map) + requires + forall|k| #[trigger] mymap.contains_key(k) ==> mymap[k] > 0, + ensures + #[trigger] Multiset::from_map(mymap).dom() == mymap.dom(), +{ + broadcast use { + super::set::group_set_lemmas, + Multiset::dom_ensures, + axiom_multiset_contained, + axiom_multiset_new_not_contained, + }; + + let lhs = Multiset::from_map(mymap).dom(); + let rhs = mymap.dom(); + assert forall|v: V| lhs.contains(v) <==> rhs.contains(v) by { + if lhs.contains(v) { + assert(Multiset::from_map(mymap).count(v) > 0); + if !mymap.dom().contains(v) { + axiom_multiset_new_not_contained(mymap, v); + assert(false); + } + } + if rhs.contains(v) { + assert(mymap.dom().contains(v)); + assert(mymap.contains_key(v)); + axiom_multiset_contained(mymap, v); + assert(mymap[v] > 0); + assert(Multiset::from_map(mymap).count(v) > 0); + assert(lhs.contains(v)); + } + }; + axiom_set_ext_equal(lhs, rhs); +} + // Specification of `singleton` /// A call to Multiset::singleton with input value `v` will return a multiset that maps /// value `v` to multiplicity 1. @@ -326,19 +387,11 @@ pub broadcast axiom fn axiom_choose_count(m: Multiset) #[trigger] m.count(m.choose()) > 0, ; -// Axiom about finiteness -/// The domain of a multiset (the set of all values that map to a multiplicity greater than 0) is always finite. -// NB this axiom's soundness depends on the inability to learn anything about the entirety of -// Multiset::from_map.dom(). -pub broadcast axiom fn axiom_multiset_always_finite(m: Multiset) - ensures - #[trigger] m.dom().finite(), -; - pub broadcast group group_multiset_axioms { axiom_multiset_empty, axiom_multiset_contained, axiom_multiset_new_not_contained, + lemma_from_map_dom, axiom_multiset_singleton, axiom_multiset_singleton_different, axiom_multiset_add, @@ -352,7 +405,6 @@ pub broadcast group group_multiset_axioms { axiom_count_le_len, axiom_filter_count, axiom_choose_count, - axiom_multiset_always_finite, } // Lemmas about `update` @@ -362,18 +414,27 @@ pub broadcast proof fn lemma_update_same(m: Multiset, v: V, mult: nat) ensures #[trigger] m.update(v, mult).count(v) == mult, { - broadcast use {group_set_axioms, group_map_axioms, group_multiset_axioms}; - - let map = Map::new( - |key: V| (m.contains(key) || key == v), - |key: V| - if key == v { - mult - } else { - m.count(key) - }, - ); - assert(map.dom() =~= m.dom().insert(v)); + broadcast use { + group_set_lemmas, + super::map::group_map_lemmas, + group_multiset_axioms, + Multiset::dom_ensures, + }; + + reveal(Multiset::update); + let key_set = m.dom().insert(v); + let fv = |key: V| + if key == v { + mult + } else { + m.count(key) + }; + let map = Map::new(key_set, fv); + crate::vstd::map_lib::lemma_map_new_domain(key_set, fv); + assert(key_set.contains(v)); + assert(map.dom().contains(v)); + assert(map[v] == mult); + axiom_multiset_contained(map, v); } /// The multiset resulting from updating a value `v1` in a multiset `m` to multiplicity `mult` will @@ -384,18 +445,34 @@ pub broadcast proof fn lemma_update_different(m: Multiset, v1: V, mult: na ensures #[trigger] m.update(v1, mult).count(v2) == m.count(v2), { - broadcast use {group_set_axioms, group_map_axioms, group_multiset_axioms}; - - let map = Map::new( - |key: V| (m.contains(key) || key == v1), - |key: V| - if key == v1 { - mult - } else { - m.count(key) - }, - ); - assert(map.dom() =~= m.dom().insert(v1)); + broadcast use {group_set_lemmas, super::map::group_map_lemmas, group_multiset_axioms}; + broadcast use {axiom_multiset_contained, Multiset::dom_ensures}; + + reveal(Multiset::update); + let key_set = m.dom().insert(v1); + let fv = |key: V| + if key == v1 { + mult + } else { + m.count(key) + }; + let map = Map::new(key_set, fv); + crate::vstd::map_lib::lemma_map_new_domain(key_set, fv); + if map.dom().contains(v2) { + assert(map[v2] == m.count(v2)); + axiom_multiset_contained(map, v2); + } else { + axiom_multiset_new_not_contained(map, v2); + assert(!m.dom().contains(v2)) by { + if m.dom().contains(v2) { + assert(key_set.contains(v2)); + assert(map.dom().contains(v2)); + assert(false); + } + } + m.dom_ensures(); + assert(m.count(v2) == 0); + } } // Lemmas about `insert` @@ -461,13 +538,30 @@ pub broadcast proof fn lemma_intersection_count(a: Multiset, b: Multiset::new( - |v: V| a.contains(v), + let m = Map::new(a.dom(), |v: V| min(a.count(v) as int, b.count(v) as int) as nat); + crate::vstd::map_lib::lemma_map_new_domain( + a.dom(), |v: V| min(a.count(v) as int, b.count(v) as int) as nat, ); - assert(m.dom() =~= a.dom()); + if m.dom().contains(x) { + assert(m[x] == min(a.count(x) as int, b.count(x) as int) as nat); + axiom_multiset_contained(m, x); + } else { + axiom_multiset_new_not_contained(m, x); + assert(!a.dom().contains(x)); + a.dom_ensures(); + assert(a.count(x) == 0) by { + if a.count(x) != 0 { + assert(a.count(x) > 0); + assert(a.dom().contains(x)); + assert(false); + } + } + assert(min(a.count(x) as int, b.count(x) as int) == 0); + } } // This verified lemma used to be an axiom in the Dafny prelude @@ -530,10 +624,31 @@ pub broadcast proof fn lemma_difference_count(a: Multiset, b: Multiset, ensures #[trigger] a.difference_with(b).count(x) == clip(a.count(x) - b.count(x)), { - broadcast use {group_set_axioms, group_map_axioms, group_multiset_axioms}; + broadcast use { + group_set_lemmas, + super::map::group_map_lemmas, + group_multiset_axioms, + Multiset::dom_ensures, + }; - let m = Map::::new(|v: V| a.contains(v), |v: V| clip(a.count(v) - b.count(v))); - assert(m.dom() =~= a.dom()); + let map = Map::::new(a.dom(), |v: V| clip(a.count(v) - b.count(v))); + crate::vstd::map_lib::lemma_map_new_domain(a.dom(), |v: V| clip(a.count(v) - b.count(v))); + if map.dom().contains(x) { + assert(map[x] == clip(a.count(x) - b.count(x))); + axiom_multiset_contained(map, x); + } else { + axiom_multiset_new_not_contained(map, x); + assert(!a.dom().contains(x)); + a.dom_ensures(); + assert(a.count(x) == 0) by { + if a.count(x) != 0 { + assert(a.count(x) > 0); + assert(a.dom().contains(x)); + assert(false); + } + } + assert(clip(a.count(x) - b.count(x)) == 0); + } } // This verified lemma used to be an axiom in the Dafny prelude @@ -591,6 +706,7 @@ macro_rules! assert_multisets_equal_internal { pub broadcast group group_multiset_properties { lemma_update_same, lemma_update_different, + lemma_multiset_empty_len, lemma_insert_containment, lemma_insert_increases_count_by_1, lemma_insert_non_decreasing, @@ -601,6 +717,7 @@ pub broadcast group group_multiset_properties { lemma_right_pseudo_idempotence, lemma_difference_count, lemma_difference_bottoms_out, + Multiset::dom_ensures, } #[doc(hidden)] diff --git a/source/vstd/prelude.rs b/source/vstd/prelude.rs index 4f03cc9d14..8a69d5e09c 100644 --- a/source/vstd/prelude.rs +++ b/source/vstd/prelude.rs @@ -34,12 +34,11 @@ pub use verus_builtin_macros::verus_spec; pub use verus_builtin_macros::verus_trait_impl; pub use verus_builtin_macros::verus_verify; -pub use super::map::Map; -pub use super::map::map; -pub use super::seq::Seq; -pub use super::seq::seq; -pub use super::set::Set; -pub use super::set::set; +pub use super::imap::{IMap, imap}; +pub use super::iset::{ISet, iset}; +pub use super::map::{Map, map}; +pub use super::seq::{Seq, seq}; +pub use super::set::{Set, set}; pub use super::view::*; #[cfg(verus_keep_ghost)] diff --git a/source/vstd/raw_ptr.rs b/source/vstd/raw_ptr.rs index 7a005370c2..7d8017752e 100644 --- a/source/vstd/raw_ptr.rs +++ b/source/vstd/raw_ptr.rs @@ -779,12 +779,12 @@ impl PointsToRaw { /// Returns `true` if the domain of this permission is exactly the range `[start, start + len)`. pub open spec fn is_range(self, start: int, len: int) -> bool { - super::set_lib::set_int_range(start, start + len) =~= self.dom() + super::set::Set::range(start, start + len) =~= self.dom() } /// Returns `true` if the domain of this permission contains the range `[start, start + len)`. pub open spec fn contains_range(self, start: int, len: int) -> bool { - super::set_lib::set_int_range(start, start + len) <= self.dom() + super::set::Set::range(start, start + len) <= self.dom() } /// Constructs a `PointsToRaw` permission over an empty domain with the given provenance. diff --git a/source/vstd/relations.rs b/source/vstd/relations.rs index 61bdbb1f1b..4684419f16 100644 --- a/source/vstd/relations.rs +++ b/source/vstd/relations.rs @@ -5,8 +5,6 @@ use super::pervasive::*; use super::prelude::*; #[allow(unused_imports)] use super::seq::*; -#[allow(unused_imports)] -use super::set::Set; verus! { @@ -14,11 +12,6 @@ pub open spec fn injective(r: spec_fn(X) -> Y) -> bool { forall|x1: X, x2: X| #[trigger] r(x1) == #[trigger] r(x2) ==> x1 == x2 } -pub open spec fn injective_on(r: spec_fn(X) -> Y, dom: Set) -> bool { - forall|x1: X, x2: X| - dom.contains(x1) && dom.contains(x2) && #[trigger] r(x1) == #[trigger] r(x2) ==> x1 == x2 -} - pub open spec fn commutative(r: spec_fn(T, T) -> U) -> bool { forall|x: T, y: T| #[trigger] r(x, y) == #[trigger] r(y, x) } @@ -96,32 +89,6 @@ pub open spec fn sorted_by(a: Seq, less_than: spec_fn(T, T) -> bool) -> bo forall|i: int, j: int| 0 <= i < j < a.len() ==> #[trigger] less_than(a[i], a[j]) } -/// An element in an ordered set is called a least element (or a minimum), if it is less than -/// every other element of the set. -/// -/// change f to leq bc it is a relation. also these are an ordering relation -pub open spec fn is_least(leq: spec_fn(T, T) -> bool, min: T, s: Set) -> bool { - s.contains(min) && forall|x: T| s.contains(x) ==> #[trigger] leq(min, x) -} - -/// An element in an ordered set is called a minimal element, if no other element is less than it. -pub open spec fn is_minimal(leq: spec_fn(T, T) -> bool, min: T, s: Set) -> bool { - s.contains(min) && forall|x: T| - s.contains(x) && #[trigger] leq(x, min) ==> #[trigger] leq(min, x) -} - -/// An element in an ordered set is called a greatest element (or a maximum), if it is greater than -///every other element of the set. -pub open spec fn is_greatest(leq: spec_fn(T, T) -> bool, max: T, s: Set) -> bool { - s.contains(max) && forall|x: T| s.contains(x) ==> #[trigger] leq(x, max) -} - -/// An element in an ordered set is called a maximal element, if no other element is greater than it. -pub open spec fn is_maximal(leq: spec_fn(T, T) -> bool, max: T, s: Set) -> bool { - s.contains(max) && forall|x: T| - s.contains(x) && #[trigger] leq(max, x) ==> #[trigger] leq(x, max) -} - pub proof fn lemma_new_first_element_still_sorted_by( x: T, s: Seq, @@ -143,21 +110,4 @@ pub proof fn lemma_new_first_element_still_sorted_by( } } -/// If a function is injective on a set `s2`, then it is also injective on any subset `s1` of `s2`. -pub proof fn lemma_injective_on_subset(r: spec_fn(X) -> Y, s1: Set, s2: Set) - requires - s1 <= s2, - injective_on(r, s2), - ensures - injective_on(r, s1), -{ - assert forall|x1: X, x2: X| - s1.contains(x1) && s1.contains(x2) && #[trigger] r(x1) == #[trigger] r(x2) implies x1 - == x2 by { - assert(s2.contains(x1)); - assert(s2.contains(x2)); - assert(r(x1) == r(x2)); - } -} - } // verus! diff --git a/source/vstd/resource/algebra.rs b/source/vstd/resource/algebra.rs index c009ec7294..259911f44d 100644 --- a/source/vstd/resource/algebra.rs +++ b/source/vstd/resource/algebra.rs @@ -9,7 +9,7 @@ use super::relations::*; verus! { -broadcast use super::super::set::group_set_axioms; +broadcast use super::super::iset::group_iset_lemmas; /// Interface for Resource Algebra ghost state. #[verifier::accept_recursive_types(RA)] @@ -137,7 +137,7 @@ impl Resource { /// This is a more general version of [`update`](Self::update). // GHOST-UPDATE rule - pub proof fn update_nondeterministic(tracked self, new_values: Set) -> (tracked out: Self) + pub proof fn update_nondeterministic(tracked self, new_values: ISet) -> (tracked out: Self) requires frame_preserving_update_nondeterministic_opt(self.value(), new_values), ensures @@ -164,7 +164,7 @@ impl Resource { out.loc() == self.loc(), out.value() == new_value, { - let new_values = set![new_value]; + let new_values = iset![new_value]; assert(new_values.contains(new_value)); self.update_nondeterministic(new_values) } @@ -200,7 +200,7 @@ impl Resource { pub proof fn update_nondeterministic_with_shared( tracked self, tracked other: &Resource, - new_values: Set, + new_values: ISet, ) -> (tracked out: Self) requires self.loc() == other.loc(), @@ -242,10 +242,10 @@ impl Resource { out.loc() == self.loc(), out.value() == new_value, { - let new_values = set![new_value]; + let new_values = iset![new_value]; let so = set_op(new_values, other.value()); assert(new_values.contains(new_value)); - assert(so == set![new_value].map(|n| RA::op(new_value, other.value()))); + assert(so == iset![new_value].map(|n| RA::op(new_value, other.value()))); assert(so.contains(RA::op(new_value, other.value()))); self.update_nondeterministic_with_shared(other, new_values) } diff --git a/source/vstd/resource/frac.rs b/source/vstd/resource/frac.rs index e350467363..af7086f73b 100644 --- a/source/vstd/resource/frac.rs +++ b/source/vstd/resource/frac.rs @@ -11,8 +11,6 @@ use super::*; verus! { -broadcast use {super::super::map::group_map_axioms, super::super::set::group_set_axioms}; - pub enum FractionRA { Frac(real), Invalid, diff --git a/source/vstd/resource/frac_opt.rs b/source/vstd/resource/frac_opt.rs index 9467218644..03c8a4d92b 100644 --- a/source/vstd/resource/frac_opt.rs +++ b/source/vstd/resource/frac_opt.rs @@ -5,7 +5,12 @@ use super::storage_protocol::*; verus! { -broadcast use {super::super::map::group_map_axioms, super::super::set::group_set_axioms}; +broadcast use { + super::super::imap::group_imap_lemmas, + super::super::iset::group_iset_lemmas, + super::super::map::group_map_lemmas, + super::super::set::group_set_lemmas, +}; /////// Fractional tokens that allow borrowing of resources enum FractionalCarrierOpt { @@ -36,12 +41,12 @@ impl Protocol<(), T> for FractionalCarrierOpt { } } - closed spec fn rel(self, s: Map<(), T>) -> bool { + closed spec fn rel(self, s: IMap<(), T>) -> bool { match self { FractionalCarrierOpt::Value { v, frac } => { (match v { Some(v0) => s.dom().contains(()) && s[()] == v0, - None => s =~= map![], + None => s =~= imap![], }) && frac == 1 as real }, FractionalCarrierOpt::Empty => false, @@ -106,7 +111,7 @@ impl Frac { result.resource() == v, { let f = FractionalCarrierOpt::::Value { v: Some(v), frac: (1 as real) }; - let tracked mut m = Map::<(), T>::tracked_empty(); + let tracked mut m = IMap::<(), T>::tracked_empty(); m.tracked_insert((), v); let tracked r = StorageResource::alloc(f, m); Self { r } @@ -145,7 +150,7 @@ impl Frac { r.validate(); let tracked mut r1 = StorageResource::alloc( FractionalCarrierOpt::Value { v: None, frac: (1 as real) }, - Map::tracked_empty(), + IMap::tracked_empty(), ); tracked_swap(r, &mut r1); let tracked (r1, r2) = r1.split( @@ -224,7 +229,7 @@ impl Frac { r.validate(); let tracked mut r1 = StorageResource::alloc( FractionalCarrierOpt::Value { v: None, frac: (1 as real) }, - Map::tracked_empty(), + IMap::tracked_empty(), ); tracked_swap(r, &mut r1); r1.validate_with_shared(&other.r); @@ -246,7 +251,7 @@ impl Frac { ret == self.resource(), { use_type_invariant(self); - StorageResource::guard(&self.r, map![() => self.resource()]).tracked_borrow(()) + StorageResource::guard(&self.r, imap![() => self.resource()]).tracked_borrow(()) } /// Reclaim full ownership of the underlying resource. @@ -262,17 +267,17 @@ impl Frac { let p1 = self.r.value(); let p2 = FractionalCarrierOpt::Value { v: None, frac: (1 as real) }; - let b2 = map![() => self.resource()]; - assert forall|q: FractionalCarrierOpt, t1: Map<(), T>| + let b2 = imap![() => self.resource()]; + assert forall|q: FractionalCarrierOpt, t1: IMap<(), T>| #![all_triggers] FractionalCarrierOpt::rel(FractionalCarrierOpt::op(p1, q), t1) implies exists| - t2: Map<(), T>, + t2: IMap<(), T>, | #![all_triggers] FractionalCarrierOpt::rel(FractionalCarrierOpt::op(p2, q), t2) && t2.dom().disjoint( b2.dom(), ) && t1 =~= t2.union_prefer_right(b2) by { - let t2 = map![]; + let t2 = imap![]; assert(FractionalCarrierOpt::rel(FractionalCarrierOpt::op(p2, q), t2)); assert(t2.dom().disjoint(b2.dom())); assert(t1 =~= t2.union_prefer_right(b2)); @@ -299,7 +304,7 @@ impl Empty { pub proof fn new(tracked v: T) -> (tracked result: Self) { let f = FractionalCarrierOpt::::Value { v: None, frac: (1 as real) }; - let tracked mut m = Map::<(), T>::tracked_empty(); + let tracked mut m = IMap::<(), T>::tracked_empty(); let tracked r = StorageResource::alloc(f, m); Self { r } } @@ -315,24 +320,24 @@ impl Empty { self.r.validate(); let p1 = self.r.value(); - let b1 = map![() => resource]; + let b1 = imap![() => resource]; let p2 = FractionalCarrierOpt::Value { v: Some(resource), frac: (1 as real) }; - assert forall|q: FractionalCarrierOpt, t1: Map<(), T>| + assert forall|q: FractionalCarrierOpt, t1: IMap<(), T>| #![all_triggers] FractionalCarrierOpt::rel(FractionalCarrierOpt::op(p1, q), t1) implies exists| - t2: Map<(), T>, + t2: IMap<(), T>, | #![all_triggers] FractionalCarrierOpt::rel(FractionalCarrierOpt::op(p2, q), t2) && t1.dom().disjoint( b1.dom(), ) && t1.union_prefer_right(b1) =~= t2 by { - let t2 = map![() => resource]; + let t2 = imap![() => resource]; assert(FractionalCarrierOpt::rel(FractionalCarrierOpt::op(p2, q), t2) && t1.dom().disjoint(b1.dom()) && t1.union_prefer_right(b1) =~= t2); } - let tracked mut m = Map::tracked_empty(); + let tracked mut m = IMap::tracked_empty(); m.tracked_insert((), resource); let tracked Self { r } = self; let tracked new_r = r.deposit(m, p2); diff --git a/source/vstd/resource/map.rs b/source/vstd/resource/map.rs index d18757522a..bc3b7973ec 100644 --- a/source/vstd/resource/map.rs +++ b/source/vstd/resource/map.rs @@ -73,11 +73,11 @@ //! // clients that might need to operate on different keys in this map. //! } //! ``` -use super::super::map::*; -use super::super::map_lib::*; +use super::super::imap::*; +use super::super::imap_lib::*; +use super::super::iset_lib::*; use super::super::modes::*; use super::super::prelude::*; -use super::super::set_lib::*; use super::Loc; use super::algebra::ResourceAlgebra; #[cfg(verus_keep_ghost)] @@ -89,12 +89,12 @@ use super::split_mut; verus! { -broadcast use super::super::group_vstd_default; +broadcast use {super::super::group_vstd_default, super::super::imap::group_imap_lemmas}; #[verifier::reject_recursive_types(K)] #[verifier::ext_equal] enum AuthCarrier { - Auth(Map), + Auth(IMap), Frac, Invalid, } @@ -102,7 +102,7 @@ enum AuthCarrier { #[verifier::reject_recursive_types(K)] #[verifier::ext_equal] enum FracCarrier { - Frac { owning: Map, dup: Map }, + Frac { owning: IMap, dup: IMap }, Invalid, } @@ -111,14 +111,14 @@ impl AuthCarrier { !(self is Invalid) } - spec fn map(self) -> Map + spec fn map(self) -> IMap recommends self.valid(), { match self { AuthCarrier::Auth(m) => m, - AuthCarrier::Frac => Map::empty(), - AuthCarrier::Invalid => Map::empty(), + AuthCarrier::Frac => IMap::empty(), + AuthCarrier::Invalid => IMap::empty(), } } } @@ -131,17 +131,17 @@ impl FracCarrier { } } - spec fn owning_map(self) -> Map { + spec fn owning_map(self) -> IMap { match self { FracCarrier::Frac { owning, .. } => owning, - FracCarrier::Invalid => Map::empty(), + FracCarrier::Invalid => IMap::empty(), } } - spec fn dup_map(self) -> Map { + spec fn dup_map(self) -> IMap { match self { FracCarrier::Frac { dup, .. } => dup, - FracCarrier::Invalid => Map::empty(), + FracCarrier::Invalid => IMap::empty(), } } } @@ -225,7 +225,7 @@ impl ResourceAlgebra for MapCarrier { // derive contradiction that the items are disjoint if !a.frac.owning_map().dom().disjoint(a.frac.dup_map().dom()) { // The intersection is not empty - lemma_disjoint_iff_empty_intersection( + lemma_iset_disjoint_iff_empty_intersection( a.frac.owning_map().dom(), a.frac.dup_map().dom(), ); @@ -254,7 +254,7 @@ impl PCM for MapCarrier { closed spec fn unit() -> Self { MapCarrier { auth: AuthCarrier::Frac, - frac: FracCarrier::Frac { owning: Map::empty(), dup: Map::empty() }, + frac: FracCarrier::Frac { owning: IMap::empty(), dup: IMap::empty() }, } } @@ -356,8 +356,8 @@ impl GhostMapAuth { spec fn inv(self) -> bool { &&& self.r.value().auth is Auth &&& self.r.value().frac == FracCarrier::Frac { - owning: Map::::empty(), - dup: Map::::empty(), + owning: IMap::::empty(), + dup: IMap::::empty(), } } @@ -366,13 +366,13 @@ impl GhostMapAuth { self.r.loc() } - /// Logically underlying [`Map`] - pub closed spec fn view(self) -> Map { + /// Logically underlying [`IMap`] + pub closed spec fn view(self) -> IMap { self.r.value().auth.map() } /// Domain of the [`GhostMapAuth`] - pub open spec fn dom(self) -> Set { + pub open spec fn dom(self) -> ISet { self@.dom() } @@ -386,7 +386,7 @@ impl GhostMapAuth { /// Instantiate a dummy [`GhostMapAuth`] pub proof fn dummy() -> (tracked result: GhostMapAuth) { - let tracked (auth, submap) = GhostMapAuth::::new(Map::empty()); + let tracked (auth, submap) = GhostMapAuth::::new(IMap::empty()); auth } @@ -405,14 +405,14 @@ impl GhostMapAuth { pub proof fn empty(tracked &self) -> (tracked result: GhostSubmap) ensures result.id() == self.id(), - result@ == Map::::empty(), + result@ == IMap::::empty(), { use_type_invariant(self); let tracked r = Resource::>::create_unit(self.r.loc()); GhostSubmap { r } } - /// Insert a [`Map`] of values, receiving the [`GhostSubmap`] that asserts ownership over the key + /// Insert an [`IMap`] of values, receiving the [`GhostSubmap`] that asserts ownership over the key /// domain inserted. /// /// ``` @@ -426,7 +426,7 @@ impl GhostMapAuth { /// submap /// } /// ``` - pub proof fn insert_map(tracked &mut self, m: Map) -> (tracked result: GhostSubmap) + pub proof fn insert_map(tracked &mut self, m: IMap) -> (tracked result: GhostSubmap) requires old(self)@.dom().disjoint(m.dom()), ensures @@ -447,7 +447,7 @@ impl GhostMapAuth { let full_carrier = MapCarrier { auth: AuthCarrier::Auth(self_r.value().auth.map().union_prefer_right(m)), - frac: FracCarrier::Frac { owning: m, dup: Map::empty() }, + frac: FracCarrier::Frac { owning: m, dup: IMap::empty() }, }; assert(full_carrier.valid()); @@ -455,7 +455,7 @@ impl GhostMapAuth { let auth_carrier = MapCarrier { auth: updated_r.value().auth, - frac: FracCarrier::Frac { owning: Map::empty(), dup: Map::empty() }, + frac: FracCarrier::Frac { owning: IMap::empty(), dup: IMap::empty() }, }; let frac_carrier = MapCarrier { auth: AuthCarrier::Frac, frac: updated_r.value().frac }; @@ -486,7 +486,7 @@ impl GhostMapAuth { result.id() == final(self).id(), result@ == (k, v), { - let tracked submap = self.insert_map(map![k => v]); + let tracked submap = self.insert_map(imap![k => v]); GhostPointsTo { submap } } @@ -530,7 +530,7 @@ impl GhostMapAuth { let new_r = MapCarrier { auth: AuthCarrier::Auth(new_auth_map), - frac: FracCarrier::Frac { owning: Map::empty(), dup: Map::empty() }, + frac: FracCarrier::Frac { owning: IMap::empty(), dup: IMap::empty() }, }; // update the resource @@ -562,7 +562,7 @@ impl GhostMapAuth { self.delete(p.submap); } - /// Create a new [`GhostMapAuth`] from a [`Map`]. + /// Create a new [`GhostMapAuth`] from an [`IMap`]. /// Gives the other half of ownership in the form of a [`GhostSubmap`]. /// /// ``` @@ -573,7 +573,7 @@ impl GhostMapAuth { /// assert(sub.dom() == m.dom()); /// } /// ``` - pub proof fn new(m: Map) -> (tracked result: (GhostMapAuth, GhostSubmap)) + pub proof fn new(m: IMap) -> (tracked result: (GhostMapAuth, GhostSubmap)) ensures result.0.id() == result.1.id(), result.0@ == m, @@ -582,18 +582,18 @@ impl GhostMapAuth { let tracked full_r = Resource::alloc( MapCarrier { auth: AuthCarrier::Auth(m), - frac: FracCarrier::Frac { owning: m, dup: Map::empty() }, + frac: FracCarrier::Frac { owning: m, dup: IMap::empty() }, }, ); let auth_carrier = MapCarrier { auth: AuthCarrier::Auth(m), - frac: FracCarrier::Frac { owning: Map::empty(), dup: Map::empty() }, + frac: FracCarrier::Frac { owning: IMap::empty(), dup: IMap::empty() }, }; let frac_carrier = MapCarrier { auth: AuthCarrier::Frac, - frac: FracCarrier::Frac { owning: m, dup: Map::empty() }, + frac: FracCarrier::Frac { owning: m, dup: IMap::empty() }, }; assert(full_r.value() == MapCarrier::op(auth_carrier, frac_carrier)); @@ -623,13 +623,13 @@ impl GhostSubmap { self.r.loc() } - /// Logically underlying [`Map`] - pub closed spec fn view(self) -> Map { + /// Logically underlying [`IMap`] + pub closed spec fn view(self) -> IMap { self.r.value().frac.owning_map() } /// Domain of the [`GhostSubmap`] - pub open spec fn dom(self) -> Set { + pub open spec fn dom(self) -> ISet { self@.dom() } @@ -643,7 +643,7 @@ impl GhostSubmap { /// Instantiate a dummy [`GhostSubmap`] pub proof fn dummy() -> (tracked result: GhostSubmap) { - let tracked (auth, submap) = GhostMapAuth::::new(Map::empty()); + let tracked (auth, submap) = GhostMapAuth::::new(IMap::empty()); submap } @@ -651,7 +651,7 @@ impl GhostSubmap { pub proof fn empty(tracked &self) -> (tracked result: GhostSubmap) ensures result.id() == self.id(), - result@ == Map::::empty(), + result@ == IMap::::empty(), { use_type_invariant(self); let tracked r = Resource::>::create_unit(self.r.loc()); @@ -806,7 +806,7 @@ impl GhostSubmap { } /// We can split a [`GhostSubmap`] based on a set of keys in its domain. - pub proof fn split(tracked &mut self, s: Set) -> (tracked result: GhostSubmap) + pub proof fn split(tracked &mut self, s: ISet) -> (tracked result: GhostSubmap) requires s <= old(self)@.dom(), ensures @@ -839,6 +839,29 @@ impl GhostSubmap { GhostSubmap { r } } + pub proof fn split_with_olddom( + tracked &mut self, + s: ISet, + olddom: ISet, + ) -> (tracked result: GhostSubmap) + requires + olddom == old(self)@.dom(), + s <= olddom, + ensures + final(self).id() == old(self).id(), + result.id() == final(self).id(), + old(self)@ == final(self)@.union_prefer_right(result@), + result@.dom() == s, + final(self)@.dom() == olddom - s, + { + let tracked out = self.split(s); + assert(olddom == old(self)@.dom()); + assert(self@.dom() == old(self)@.dom() - s); + assert(self@.dom() == olddom - s); + assert(out@.dom() == s); + out + } + /// We can separate a single key out of a [`GhostSubmap`] pub proof fn split_points_to(tracked &mut self, k: K) -> (tracked result: GhostPointsTo) requires @@ -852,7 +875,7 @@ impl GhostSubmap { { use_type_invariant(&*self); - let tracked submap = self.split(set![k]); + let tracked submap = self.split(iset![k]); GhostPointsTo { submap } } @@ -874,7 +897,7 @@ impl GhostSubmap { /// sub.update(map![1int => 9int, 2int => 10int, 3int => 11int]); /// } /// ``` - pub proof fn update(tracked &mut self, tracked auth: &mut GhostMapAuth, m: Map) + pub proof fn update(tracked &mut self, tracked auth: &mut GhostMapAuth, m: IMap) requires m.dom() <= old(self)@.dom(), old(self).id() == old(auth).id(), @@ -906,7 +929,7 @@ impl GhostSubmap { let auth_carrier = AuthCarrier::Auth(full_r.value().auth.map().union_prefer_right(m)); let frac_carrier = FracCarrier::Frac { owning: full_r.value().frac.owning_map().union_prefer_right(m), - dup: Map::empty(), + dup: IMap::empty(), }; let new_full_carrier = MapCarrier { auth: auth_carrier, frac: frac_carrier }; @@ -915,7 +938,7 @@ impl GhostSubmap { let new_auth_carrier = MapCarrier { auth: r_upd.value().auth, - frac: FracCarrier::Frac { owning: Map::empty(), dup: Map::empty() }, + frac: FracCarrier::Frac { owning: IMap::empty(), dup: IMap::empty() }, }; let new_frac_carrier = MapCarrier { auth: AuthCarrier::Frac, frac: r_upd.value().frac }; assert(r_upd.value().frac == MapCarrier::op(new_auth_carrier, new_frac_carrier).frac); @@ -931,7 +954,7 @@ impl GhostSubmap { requires self.is_points_to(), ensures - self@ == map![r.key() => r.value()], + self@ == imap![r.key() => r.value()], self.id() == r.id(), { let tracked r = GhostPointsTo { submap: self }; @@ -994,13 +1017,13 @@ impl GhostPersistentSubmap { self.r.loc() } - /// Logically underlying [`Map`] - pub closed spec fn view(self) -> Map { + /// Logically underlying [`IMap`] + pub closed spec fn view(self) -> IMap { self.r.value().frac.dup_map() } /// Domain of the [`GhostPersistentSubmap`] - pub open spec fn dom(self) -> Set { + pub open spec fn dom(self) -> ISet { self@.dom() } @@ -1022,7 +1045,7 @@ impl GhostPersistentSubmap { pub proof fn empty(tracked &self) -> (tracked result: GhostPersistentSubmap) ensures result.id() == self.id(), - result@ == Map::::empty(), + result@ == IMap::::empty(), { use_type_invariant(self); let tracked r = Resource::>::create_unit(self.r.loc()); @@ -1179,7 +1202,7 @@ impl GhostPersistentSubmap { } /// We can split a [`GhostPersistentSubmap`] based on a set of keys in its domain. - pub proof fn split(tracked &mut self, s: Set) -> (tracked result: GhostPersistentSubmap< + pub proof fn split(tracked &mut self, s: ISet) -> (tracked result: GhostPersistentSubmap< K, V, >) @@ -1233,7 +1256,7 @@ impl GhostPersistentSubmap { { use_type_invariant(&*self); - let tracked submap = self.split(set![k]); + let tracked submap = self.split(iset![k]); GhostPersistentPointsTo { submap } } @@ -1242,7 +1265,7 @@ impl GhostPersistentSubmap { requires self.is_points_to(), ensures - self@ == map![r.key() => r.value()], + self@ == imap![r.key() => r.value()], self.id() == r.id(), { let tracked r = GhostPersistentPointsTo { submap: self }; @@ -1320,7 +1343,7 @@ impl GhostPointsTo { self.id() == other.id(), ensures r.id() == self.id(), - r@ == map![self.key() => self.value(), other.key() => other.value()], + r@ == imap![self.key() => self.value(), other.key() => other.value()], self.key() != other.key(), { use_type_invariant(&self); @@ -1403,7 +1426,7 @@ impl GhostPointsTo { final(auth).id() == old(auth).id(), final(self).key() == old(self).key(), final(self)@ == (final(self).key(), v), - final(auth)@ == old(auth)@.union_prefer_right(map![final(self).key() => v]), + final(auth)@ == old(auth)@.union_prefer_right(imap![final(self).key() => v]), { broadcast use lemma_submap_of_trans; broadcast use lemma_submap_of_op; @@ -1413,7 +1436,7 @@ impl GhostPointsTo { let ghost old_dom = self.submap.dom(); self.lemma_map_view(); - let m = map![self.key() => v]; + let m = imap![self.key() => v]; assert(self.submap@.union_prefer_right(m) == m); self.submap.update(auth, m); } @@ -1422,7 +1445,7 @@ impl GhostPointsTo { pub proof fn submap(tracked self) -> (tracked r: GhostSubmap) ensures r.id() == self.id(), - r@ == map![self.key() => self.value()], + r@ == imap![self.key() => self.value()], { self.lemma_map_view(); self.submap @@ -1430,11 +1453,11 @@ impl GhostPointsTo { proof fn lemma_map_view(tracked &self) ensures - self.submap@ == map![self.key() => self.value()], + self.submap@ == imap![self.key() => self.value()], { use_type_invariant(self); let key = self.key(); - let target_dom = set![key]; + let target_dom = iset![key]; assert(self.submap@.dom().len() == 1); assert(target_dom.len() == 1); @@ -1449,7 +1472,7 @@ impl GhostPointsTo { assert(target_dom.remove(key).len() == 0); assert(self.submap@.dom() =~= target_dom); - assert(self.submap@ == map![self.key() => self.value()]); + assert(self.submap@ == imap![self.key() => self.value()]); } /// Can be used to learn what the key-value pair of [`GhostPointsTo`] is @@ -1551,7 +1574,7 @@ impl GhostPersistentPointsTo { self.id() == other.id(), ensures submap.id() == self.id(), - submap@ == map![self.key() => self.value(), other.key() => other.value()], + submap@ == imap![self.key() => self.value(), other.key() => other.value()], self.key() != other.key() ==> submap@.len() == 2, self.key() == other.key() ==> submap@.len() == 1, { @@ -1634,7 +1657,7 @@ impl GhostPersistentPointsTo { pub proof fn submap(tracked self) -> (tracked r: GhostPersistentSubmap) ensures r.id() == self.id(), - r@ == map![self.key() => self.value()], + r@ == imap![self.key() => self.value()], { self.lemma_map_view(); self.submap @@ -1642,11 +1665,11 @@ impl GhostPersistentPointsTo { proof fn lemma_map_view(tracked &self) ensures - self.submap@ == map![self.key() => self.value()], + self.submap@ == imap![self.key() => self.value()], { use_type_invariant(self); let key = self.key(); - let target_dom = set![key]; + let target_dom = iset![key]; assert(self.submap@.dom().len() == 1); assert(target_dom.len() == 1); @@ -1661,7 +1684,7 @@ impl GhostPersistentPointsTo { assert(target_dom.remove(key).len() == 0); assert(self.submap@.dom() =~= target_dom); - assert(self.submap@ == map![self.key() => self.value()]); + assert(self.submap@ == imap![self.key() => self.value()]); } /// Can be used to learn what the key-value pair of [`GhostPersistentPointsTo`] is diff --git a/source/vstd/resource/option.rs b/source/vstd/resource/option.rs index baa2b310fc..50839f77d5 100644 --- a/source/vstd/resource/option.rs +++ b/source/vstd/resource/option.rs @@ -86,11 +86,11 @@ pub proof fn lemma_incl_opt_rev(a: RA, b: RA) } } -pub proof fn lemma_set_op_opt(s: Set, t: RA) +pub proof fn lemma_set_op_opt(s: ISet, t: RA) ensures set_op(s, t).map(|b| Some(b)) == set_op(s.map(|x| Some(x)), Some(t)), { - broadcast use super::super::set::group_set_axioms; + broadcast use super::super::iset::group_iset_lemmas; let s_mapped = s.map(|x| Some(x)); let original = set_op(s, t); diff --git a/source/vstd/resource/pcm.rs b/source/vstd/resource/pcm.rs index cd8ef9331e..b7f1f2e4db 100644 --- a/source/vstd/resource/pcm.rs +++ b/source/vstd/resource/pcm.rs @@ -8,7 +8,7 @@ use super::super::modes::tracked_swap; verus! { -broadcast use super::super::set::group_set_axioms; +broadcast use super::super::iset::group_iset_lemmas; /// Interface for PCM / Resource Algebra ghost state. /// @@ -142,7 +142,7 @@ impl Resource

{ /// This is a more general version of [`update`](Self::update). // GHOST-UPDATE rule - pub proof fn update_nondeterministic(tracked self, new_values: Set

) -> (tracked out: Self) + pub proof fn update_nondeterministic(tracked self, new_values: ISet

) -> (tracked out: Self) requires frame_preserving_update_nondeterministic(self.value(), new_values), ensures @@ -180,7 +180,7 @@ impl Resource

{ out.loc() == self.loc(), out.value() == new_value, { - let new_values = set![new_value]; + let new_values = iset![new_value]; assert(new_values.contains(new_value)); self.update_nondeterministic(new_values) } @@ -235,7 +235,7 @@ impl Resource

{ pub axiom fn update_nondeterministic_with_shared( tracked self, tracked other: &Self, - new_values: Set

, + new_values: ISet

, ) -> (tracked out: Self) requires self.loc() == other.loc(), @@ -284,7 +284,7 @@ impl Resource

{ out.loc() == self.loc(), out.value() == new_value, { - let new_values = set![new_value]; + let new_values = iset![new_value]; let so = set_op(new_values, other.value()); assert(so.contains(P::op(new_value, other.value()))); self.update_nondeterministic_with_shared(other, new_values) diff --git a/source/vstd/resource/relations.rs b/source/vstd/resource/relations.rs index a5d447d1fc..8ae4ae6af9 100644 --- a/source/vstd/resource/relations.rs +++ b/source/vstd/resource/relations.rs @@ -21,7 +21,7 @@ pub open spec fn frame_preserving_update(a: P, b: P) -> bool { } /// A nondeterministic version of a [`frame_preserving_update`]. -pub open spec fn frame_preserving_update_nondeterministic(a: P, bs: Set

) -> bool { +pub open spec fn frame_preserving_update_nondeterministic(a: P, bs: ISet

) -> bool { forall|c| #![trigger P::op(a, c)] P::op(a, c).valid() ==> exists|b| #[trigger] bs.contains(b) && P::op(b, c).valid() @@ -40,7 +40,7 @@ pub open spec fn frame_preserving_update_opt(a: RA, b: RA) /// A nondeterministic version of a [`frame_preserving_update_opt`]. pub open spec fn frame_preserving_update_nondeterministic_opt( a: RA, - bs: Set, + bs: ISet, ) -> bool { forall|c| #![trigger Option::::op(Some(a), c)] @@ -49,8 +49,8 @@ pub open spec fn frame_preserving_update_nondeterministic_opt(s: Set, t: RA) -> Set { - Set::new(|v| exists|q| s.contains(q) && v == RA::op(q, t)) +pub open spec fn set_op(s: ISet, t: RA) -> ISet { + ISet::new(|v| exists|q| s.contains(q) && v == RA::op(q, t)) } pub proof fn lemma_incl_transitive(a: RA, b: RA, c: RA) @@ -77,14 +77,14 @@ pub proof fn lemma_frame_preserving_opt(a: RA, b: RA) pub proof fn lemma_frame_preserving_update_nondeterministic_opt( a: RA, - bs: Set, + bs: ISet, ) requires frame_preserving_update_nondeterministic_opt::(a, bs), ensures frame_preserving_update_nondeterministic::>(Some(a), bs.map(|b| Some(b))), { - broadcast use super::super::set::group_set_axioms; + broadcast use super::super::iset::group_iset_lemmas; let bs_mapped = bs.map(|b| Some(b)); assert forall|c| diff --git a/source/vstd/resource/seq.rs b/source/vstd/resource/seq.rs index d61ac94074..a63aa4e072 100644 --- a/source/vstd/resource/seq.rs +++ b/source/vstd/resource/seq.rs @@ -7,8 +7,8 @@ verus! { broadcast use super::super::group_vstd_default; -pub open spec fn seq_to_map(s: Seq, off: int) -> Map { - Map::new(|i: int| off <= i < off + s.len(), |i: int| s[i - off]) +pub open spec fn seq_to_map(s: Seq, off: int) -> IMap { + IMap::new(|i: int| off <= i < off + s.len(), |i: int| s[i - off]) } /** An implementation of a resource for owning a subrange of a sequence. @@ -77,7 +77,7 @@ pub tracked struct GhostSubseq { impl GhostSeqAuth { #[verifier::type_invariant] spec fn inv(self) -> bool { - &&& self.auth@.dom() =~= Set::new(|i: int| self.off <= i < self.off + self.len) + &&& self.auth@.dom() =~= ISet::new(|i: int| self.off <= i < self.off + self.len) } pub closed spec fn id(self) -> Loc { @@ -175,7 +175,7 @@ impl GhostSeqAuth { impl GhostSubseq { #[verifier::type_invariant] spec fn inv(self) -> bool { - &&& self.frac@.dom() =~= Set::new(|i: int| self.off <= i < self.off + self.len) + &&& self.frac@.dom() =~= ISet::new(|i: int| self.off <= i < self.off + self.len) } pub closed spec fn view(self) -> Seq { @@ -299,7 +299,7 @@ impl GhostSubseq { final(self).off() == old(self).off(), final(auth).id() == old(auth).id(), final(self)@ =~= v, - final(auth)@ =~= Map::new( + final(auth)@ =~= IMap::new( |i: int| old(auth)@.contains_key(i), |i: int| if final(self).off() <= i < final(self).off() + v.len() { @@ -337,7 +337,7 @@ impl GhostSubseq { let tracked mut mselffrac = mself.frac; let tracked mfrac = mselffrac.split( - Set::new(|i: int| mself.off + n <= i < mself.off + mself.len), + ISet::new(|i: int| mself.off + n <= i < mself.off + mself.len), ); let tracked result = GhostSubseq { off: (mself.off + n) as nat, @@ -396,7 +396,7 @@ impl GhostSubseq { pub proof fn new(off: nat, len: nat, tracked f: GhostSubmap) -> (tracked result: GhostSubseq) requires - f@.dom() == Set::new(|i: int| off <= i < off + len), + f@.dom() == ISet::new(|i: int| off <= i < off + len), ensures result.id() == f.id(), result.off() == off, diff --git a/source/vstd/resource/set.rs b/source/vstd/resource/set.rs index 9046c1cc08..630350d1eb 100644 --- a/source/vstd/resource/set.rs +++ b/source/vstd/resource/set.rs @@ -132,12 +132,12 @@ impl GhostSetAuth { self.map.id() } - /// Logically underlying [`Set`] - pub closed spec fn view(self) -> Set { + /// Logically underlying [`ISet`] + pub closed spec fn view(self) -> ISet { self.map@.dom() } - /// Create a new [`GhostSetAuth`] from a [`Set`]. + /// Create a new [`GhostSetAuth`] from a [`ISet`]. /// Gives the other half of ownership in the form of a [`GhostSubset`] /// /// ``` @@ -148,7 +148,7 @@ impl GhostSetAuth { /// assert(sub@ == auth@); /// } /// ``` - pub proof fn new(s: Set) -> (tracked result: (GhostSetAuth, GhostSubset)) + pub proof fn new(s: ISet) -> (tracked result: (GhostSetAuth, GhostSubset)) ensures result.0.id() == result.1.id(), result.0@ == s, @@ -178,13 +178,13 @@ impl GhostSetAuth { pub proof fn empty(tracked &self) -> (tracked result: GhostSubset) ensures result.id() == self.id(), - result@ == Set::::empty(), + result@ == ISet::::empty(), { let tracked map = self.map.empty(); GhostSubset { map } } - /// Insert a [`Set`] of values, receiving the [`GhostSubset`] that asserts ownership over the set inserted. + /// Insert a [`ISet`] of values, receiving the [`GhostSubset`] that asserts ownership over the set inserted. /// /// ``` /// proof fn insert_set_example(tracked mut m: GhostSetAuth) -> (tracked r: GhostSubset) @@ -197,7 +197,7 @@ impl GhostSetAuth { /// subset /// } /// ``` - pub proof fn insert_set(tracked &mut self, s: Set) -> (tracked result: GhostSubset) + pub proof fn insert_set(tracked &mut self, s: ISet) -> (tracked result: GhostSubset) requires old(self)@.disjoint(s), ensures @@ -298,8 +298,8 @@ impl GhostSubset { self.map.id() } - /// Logically underlying [`Set`] - pub closed spec fn view(self) -> Set { + /// Logically underlying [`ISet`] + pub closed spec fn view(self) -> ISet { self.map@.dom() } @@ -313,7 +313,7 @@ impl GhostSubset { pub proof fn empty(tracked &self) -> (tracked result: GhostSubset) ensures result.id() == self.id(), - result@ == Set::::empty(), + result@ == ISet::::empty(), { let tracked map = self.map.empty(); GhostSubset { map } @@ -440,7 +440,7 @@ impl GhostSubset { } /// We can split a [`GhostSubset`] based on a set of values - pub proof fn split(tracked &mut self, s: Set) -> (tracked result: GhostSubset) + pub proof fn split(tracked &mut self, s: ISet) -> (tracked result: GhostSubset) requires s <= old(self)@, ensures @@ -474,7 +474,7 @@ impl GhostSubset { requires self.is_singleton(), ensures - self@ == set![r@], + self@ == iset![r@], self.id() == r.id(), { let tracked map = self.map.points_to(); @@ -506,8 +506,8 @@ impl GhostPersistentSubset { self.map.id() } - /// Logically underlying [`Set`] - pub closed spec fn view(self) -> Set { + /// Logically underlying [`ISet`] + pub closed spec fn view(self) -> ISet { self.map@.dom() } @@ -521,7 +521,7 @@ impl GhostPersistentSubset { pub proof fn empty(tracked &self) -> (tracked result: GhostPersistentSubset) ensures result.id() == self.id(), - result@ == Set::::empty(), + result@ == ISet::::empty(), { let tracked map = self.map.empty(); GhostPersistentSubset { map } @@ -616,7 +616,7 @@ impl GhostPersistentSubset { } /// We can split a [`GhostPersistentSubset`] based on a set of keys in its domain. - pub proof fn split(tracked &mut self, s: Set) -> (tracked result: GhostPersistentSubset) + pub proof fn split(tracked &mut self, s: ISet) -> (tracked result: GhostPersistentSubset) requires s <= old(self)@, ensures @@ -651,7 +651,7 @@ impl GhostPersistentSubset { requires self.is_singleton(), ensures - self@ == set![r@], + self@ == iset![r@], self.id() == r.id(), { let tracked map = self.map.points_to(); @@ -705,7 +705,7 @@ impl GhostSingleton { self.id() == other.id(), ensures r.id() == self.id(), - r@ == set![self@, other@], + r@ == iset![self@, other@], self@ != other@, { let tracked map = self.map.combine(other.map); @@ -767,7 +767,7 @@ impl GhostSingleton { pub proof fn subset(tracked self) -> (tracked r: GhostSubset) ensures r.id() == self.id(), - r@ == set![self@], + r@ == iset![self@], { let tracked map = self.map.submap(); GhostSubset { map } @@ -838,7 +838,7 @@ impl GhostPersistentSingleton { self.id() == other.id(), ensures r.id() == self.id(), - r@ == set![self@, other@], + r@ == iset![self@, other@], self@ != other@ ==> r@.len() == 2, self@ == other@ ==> r@.len() == 1, { @@ -874,7 +874,7 @@ impl GhostPersistentSingleton { pub proof fn subset(tracked self) -> (tracked r: GhostPersistentSubset) ensures r.id() == self.id(), - r@ == set![self@], + r@ == iset![self@], { let tracked map = self.map.submap(); GhostPersistentSubset { map } diff --git a/source/vstd/resource/storage_protocol.rs b/source/vstd/resource/storage_protocol.rs index 35f7980a2a..d6948f3613 100644 --- a/source/vstd/resource/storage_protocol.rs +++ b/source/vstd/resource/storage_protocol.rs @@ -3,7 +3,7 @@ use super::Loc; verus! { -broadcast use {super::super::set::group_set_axioms, super::super::map::group_map_axioms}; +broadcast use {super::super::iset::group_iset_lemmas, super::super::imap::group_imap_lemmas}; /// Interface for "storage protocol" ghost state. /// This is an extension-slash-variant on the more well-known concept @@ -18,7 +18,7 @@ broadcast use {super::super::set::group_set_axioms, super::super::map::group_map /// /// /// The reference version requires two monoids, the "protocol monoid" and the "base monoid". -/// In this interface, we fix the base monoid to be of the form [`Map`](crate::map::Map). +/// In this interface, we fix the base monoid to be of the form [`IMap`](crate::imap::IMap). /// (with composition of overlapping maps being undefined), which has all the necessary properties. /// Note that there's no `create_unit` (it's not sound to do this for an arbitrary location unless you /// already know a protocol was initialized at that location). @@ -32,7 +32,7 @@ broadcast use {super::super::set::group_set_axioms, super::super::map::group_map #[verifier::accept_recursive_types(V)] pub tracked struct StorageResource { _p: core::marker::PhantomData<(K, V, P)>, - _send_sync: super::super::state_machine_internal::SyncSendIfSyncSend>, + _send_sync: super::super::state_machine_internal::SyncSendIfSyncSend>, } /// See [`StorageResource`] for more information. @@ -41,7 +41,7 @@ pub trait Protocol: Sized { /// Note that `rel`, in contrast to [`PCM::valid`](crate::resource::algebra::ResourceAlgebra::valid), is not /// necessarily closed under inclusion. - spec fn rel(self, s: Map) -> bool; + spec fn rel(self, s: IMap) -> bool; spec fn unit() -> Self; @@ -65,19 +65,19 @@ pub open spec fn incl>(a: P, b: P) -> bool { exists|c| P::op(a, c) == b } -pub open spec fn guards>(p: P, b: Map) -> bool { - forall|q: P, t: Map| #![all_triggers] P::rel(P::op(p, q), t) ==> b.submap_of(t) +pub open spec fn guards>(p: P, b: IMap) -> bool { + forall|q: P, t: IMap| #![all_triggers] P::rel(P::op(p, q), t) ==> b.submap_of(t) } pub open spec fn exchanges>( p1: P, - b1: Map, + b1: IMap, p2: P, - b2: Map, + b2: IMap, ) -> bool { - forall|q: P, t1: Map| + forall|q: P, t1: IMap| #![all_triggers] - P::rel(P::op(p1, q), t1) ==> exists|t2: Map| + P::rel(P::op(p1, q), t1) ==> exists|t2: IMap| #![all_triggers] P::rel(P::op(p2, q), t2) && t1.dom().disjoint(b1.dom()) && t2.dom().disjoint(b2.dom()) && t1.union_prefer_right(b1) =~= t2.union_prefer_right(b2) @@ -85,31 +85,31 @@ pub open spec fn exchanges>( pub open spec fn exchanges_nondeterministic>( p1: P, - s1: Map, - new_values: Set<(P, Map)>, + s1: IMap, + new_values: ISet<(P, IMap)>, ) -> bool { - forall|q: P, t1: Map| + forall|q: P, t1: IMap| #![all_triggers] - P::rel(P::op(p1, q), t1) ==> exists|p2: P, s2: Map, t2: Map| + P::rel(P::op(p1, q), t1) ==> exists|p2: P, s2: IMap, t2: IMap| #![all_triggers] new_values.contains((p2, s2)) && P::rel(P::op(p2, q), t2) && t1.dom().disjoint(s1.dom()) && t2.dom().disjoint(s2.dom()) && t1.union_prefer_right(s1) =~= t2.union_prefer_right(s2) } -pub open spec fn deposits>(p1: P, b1: Map, p2: P) -> bool { - forall|q: P, t1: Map| +pub open spec fn deposits>(p1: P, b1: IMap, p2: P) -> bool { + forall|q: P, t1: IMap| #![all_triggers] - P::rel(P::op(p1, q), t1) ==> exists|t2: Map| + P::rel(P::op(p1, q), t1) ==> exists|t2: IMap| #![all_triggers] P::rel(P::op(p2, q), t2) && t1.dom().disjoint(b1.dom()) && t1.union_prefer_right(b1) =~= t2 } -pub open spec fn withdraws>(p1: P, p2: P, b2: Map) -> bool { - forall|q: P, t1: Map| +pub open spec fn withdraws>(p1: P, p2: P, b2: IMap) -> bool { + forall|q: P, t1: IMap| #![all_triggers] - P::rel(P::op(p1, q), t1) ==> exists|t2: Map| + P::rel(P::op(p1, q), t1) ==> exists|t2: IMap| #![all_triggers] P::rel(P::op(p2, q), t2) && t2.dom().disjoint(b2.dom()) && t1 =~= t2.union_prefer_right( b2, @@ -117,15 +117,15 @@ pub open spec fn withdraws>(p1: P, p2: P, b2: Map) } pub open spec fn updates>(p1: P, p2: P) -> bool { - forall|q: P, t1: Map| + forall|q: P, t1: IMap| #![all_triggers] P::rel(P::op(p1, q), t1) ==> P::rel(P::op(p2, q), t1) } -pub open spec fn set_op>(s: Set<(P, Map)>, t: P) -> Set< - (P, Map), +pub open spec fn set_op>(s: ISet<(P, IMap)>, t: P) -> ISet< + (P, IMap), > { - Set::new(|v: (P, Map)| exists|q| s.contains((q, v.1)) && v.0 == #[trigger] P::op(q, t)) + ISet::new(|v: (P, IMap)| exists|q| s.contains((q, v.1)) && v.0 == #[trigger] P::op(q, t)) } impl> StorageResource { @@ -133,7 +133,7 @@ impl> StorageResource { pub uninterp spec fn loc(self) -> Loc; - pub axiom fn alloc(p: P, tracked s: Map) -> (tracked out: Self) + pub axiom fn alloc(p: P, tracked s: IMap) -> (tracked out: Self) requires P::rel(p, s), ensures @@ -162,7 +162,7 @@ impl> StorageResource { /// is defined as the inclusion-closure of invariant, i.e., an element /// is valid if there exists another element `x` that, added to it, /// meets the invariant. - pub axiom fn validate(tracked self: &Self) -> (out: (P, Map)) + pub axiom fn validate(tracked self: &Self) -> (out: (P, IMap)) ensures ({ let (q, t) = out; @@ -174,10 +174,10 @@ impl> StorageResource { /// Most general kind of update, potentially depositing and withdrawing pub proof fn exchange( tracked p: Self, - tracked s: Map, + tracked s: IMap, new_p_value: P, - new_s_value: Map, - ) -> (tracked out: (Self, Map)) + new_s_value: IMap, + ) -> (tracked out: (Self, IMap)) requires exchanges(p.value(), s, new_p_value, new_s_value), ensures @@ -186,23 +186,24 @@ impl> StorageResource { new_p.loc() == p.loc() && new_p.value() == new_p_value && new_s == new_s_value }), { - let se = set![(new_p_value, new_s_value)]; + let se = iset![(new_p_value, new_s_value)]; Self::exchange_nondeterministic(p, s, se) } - pub proof fn deposit(tracked self, tracked base: Map, new_value: P) -> (tracked out: Self) + pub proof fn deposit(tracked self, tracked base: IMap, new_value: P) -> (tracked out: + Self) requires deposits(self.value(), base, new_value), ensures out.loc() == self.loc(), out.value() == new_value, { - Self::exchange(self, base, new_value, Map::empty()).0 + Self::exchange(self, base, new_value, IMap::empty()).0 } - pub proof fn withdraw(tracked self, new_value: P, new_base: Map) -> (tracked out: ( + pub proof fn withdraw(tracked self, new_value: P, new_base: IMap) -> (tracked out: ( Self, - Map, + IMap, )) requires withdraws(self.value(), new_value, new_base), @@ -211,7 +212,7 @@ impl> StorageResource { out.0.value() == new_value, out.1 == new_base, { - Self::exchange(self, Map::tracked_empty(), new_value, new_base) + Self::exchange(self, IMap::tracked_empty(), new_value, new_base) } /// "Normal" update, no depositing or withdrawing @@ -222,14 +223,14 @@ impl> StorageResource { out.loc() == self.loc(), out.value() == new_value, { - Self::exchange(self, Map::tracked_empty(), new_value, Map::empty()).0 + Self::exchange(self, IMap::tracked_empty(), new_value, IMap::empty()).0 } pub proof fn exchange_nondeterministic( tracked p: Self, - tracked s: Map, - new_values: Set<(P, Map)>, - ) -> (tracked out: (Self, Map)) + tracked s: IMap, + new_values: ISet<(P, IMap)>, + ) -> (tracked out: (Self, IMap)) requires exchanges_nondeterministic(p.value(), s, new_values), ensures @@ -241,7 +242,7 @@ impl> StorageResource { P::op_unit(p.value()); let tracked (selff, unit) = p.split(p.value(), P::unit()); let new_values0 = set_op(new_values, P::unit()); - super::super::set_lib::assert_sets_equal!(new_values0, new_values, v => { + super::super::iset_lib::assert_isets_equal!(new_values0, new_values, v => { P::op_unit(v.0); if new_values.contains(v) { assert(new_values0.contains(v)); @@ -255,7 +256,7 @@ impl> StorageResource { Self::exchange_nondeterministic_with_shared(selff, &unit, s, new_values) } - pub axiom fn guard(tracked p: &Self, s_value: Map) -> (tracked s: &Map) + pub axiom fn guard(tracked p: &Self, s_value: IMap) -> (tracked s: &IMap) requires guards(p.value(), s_value), ensures @@ -283,7 +284,7 @@ impl> StorageResource { pub axiom fn validate_with_shared(tracked self: &mut Self, tracked x: &Self) -> (res: ( P, - Map, + IMap, )) requires old(self).loc() == x.loc(), @@ -300,10 +301,10 @@ impl> StorageResource { pub proof fn exchange_with_shared( tracked p: Self, tracked x: &Self, - tracked s: Map, + tracked s: IMap, new_p_value: P, - new_s_value: Map, - ) -> (tracked out: (Self, Map)) + new_s_value: IMap, + ) -> (tracked out: (Self, IMap)) requires p.loc() == x.loc(), exchanges(P::op(p.value(), x.value()), s, P::op(new_p_value, x.value()), new_s_value), @@ -312,7 +313,7 @@ impl> StorageResource { out.0.value() == new_p_value, out.1 == new_s_value, { - let se = set![(new_p_value, new_s_value)]; + let se = iset![(new_p_value, new_s_value)]; Self::exchange_nondeterministic_with_shared(p, x, s, se) } @@ -322,9 +323,9 @@ impl> StorageResource { pub axiom fn exchange_nondeterministic_with_shared( tracked p: Self, tracked x: &Self, - tracked s: Map, - new_values: Set<(P, Map)>, - ) -> (tracked out: (Self, Map)) + tracked s: IMap, + new_values: ISet<(P, IMap)>, + ) -> (tracked out: (Self, IMap)) requires p.loc() == x.loc(), exchanges_nondeterministic( diff --git a/source/vstd/seq_lib.rs b/source/vstd/seq_lib.rs index 46a3fe9a83..66dbd1ef68 100644 --- a/source/vstd/seq_lib.rs +++ b/source/vstd/seq_lib.rs @@ -11,7 +11,7 @@ use super::relations::*; #[allow(unused_imports)] use super::seq::*; #[allow(unused_imports)] -use super::set::Set; +use super::set::*; verus! { @@ -21,14 +21,12 @@ impl Seq { /// Applies the function `f` to each element of the sequence, and returns /// the resulting sequence. /// The `int` parameter of `f` is the index of the element being mapped. - // TODO(verus): rename to map_entries, for consistency with Map::map pub open spec fn map(self, f: spec_fn(int, A) -> B) -> Seq { Seq::new(self.len(), |i: int| f(i, self[i])) } /// Applies the function `f` to each element of the sequence, and returns /// the resulting sequence. - // TODO(verus): rename to map, because this is what everybody wants. pub open spec fn map_values(self, f: spec_fn(A) -> B) -> Seq { Seq::new(self.len(), |i: int| f(self[i])) } @@ -528,8 +526,45 @@ impl Seq { } /// Converts a sequence into a set - pub open spec fn to_set(self) -> Set { - Set::new(|a: A| self.contains(a)) + pub closed spec fn to_set(self) -> Set { + Set::range(0, self.len() as int).map(|i| self.index(i)) + } + + pub broadcast proof fn to_set_ensures(self) + ensures + #![trigger(self.to_set())] + // to_set works for all indices + forall|i| + 0 <= i < self.len() ==> #[trigger] self.to_set().contains(self[i]), + // to_set finds everything .contains finds + forall|a| #[trigger] self.to_set().contains(a) <==> self.contains(a), + { + broadcast use super::set::group_set_lemmas; + broadcast use super::set_lib::range_set_properties; + + assert forall|i| 0 <= i < self.len() implies #[trigger] self.to_set().contains(self[i]) by { + Set::range(0, self.len() as int).lemma_map_contains(|i: int| self.index(i), self[i]); + assert(Set::range(0, self.len() as int).contains(i)); + assert(self.to_set().contains(self[i])); + } + assert forall|a| #[trigger] self.to_set().contains(a) <==> self.contains(a) by { + Set::range(0, self.len() as int).lemma_map_contains(|i: int| self.index(i), a); + if self.to_set().contains(a) { + let i = choose|i: int| #[trigger] + Set::range(0, self.len() as int).contains(i) && self.index(i) == a; + assert(0 <= i < self.len()); + assert(self.contains(a)); + } + if self.contains(a) { + let i = choose|i: int| 0 <= i < self.len() && self[i] == a; + assert(self.to_set().contains(self[i])); + assert(a == self[i]); + } + } + } + + pub open spec fn to_iset(self) -> ISet { + self.to_set().to_iset() } /// Converts a sequence into a multiset @@ -1044,7 +1079,7 @@ impl Seq { self.len() == self.to_set().len(), decreases self.len(), { - broadcast use super::set::group_set_axioms; + broadcast use super::set::group_set_lemmas; seq_to_set_equal_rec::(self); if self.len() == 0 { @@ -1052,8 +1087,10 @@ impl Seq { let rest = self.drop_last(); rest.unique_seq_to_set(); seq_to_set_equal_rec::(rest); - seq_to_set_rec_is_finite::(rest); - assert(!seq_to_set_rec(rest).contains(self.last())); + assert(!rest.contains(self.last())); + assert(!seq_to_set_rec(rest).contains(self.last())) by { + seq_to_set_rec_contains::(rest); + } assert(seq_to_set_rec(rest).insert(self.last()).len() == seq_to_set_rec(rest).len() + 1); } @@ -1064,17 +1101,14 @@ impl Seq { pub proof fn lemma_cardinality_of_set(self) ensures self.to_set().len() <= self.len(), - decreases self.len(), { - broadcast use {super::set::group_set_axioms, seq_to_set_is_finite}; - broadcast use group_seq_properties; - broadcast use super::set_lib::group_set_properties; + broadcast use super::set_lib::range_set_properties; - if self.len() == 0 { - } else { - assert(self.drop_last().to_set().insert(self.last()) =~= self.to_set()); - self.drop_last().lemma_cardinality_of_set(); - } + super::set_lib::lemma_map_size_bound::( + Set::range(0, self.len() as int), + self.to_set(), + |i: int| self.index(i), + ); } /// A sequence is of length 0 if and only if its conversion to @@ -1083,7 +1117,9 @@ impl Seq { ensures self.to_set().len() == 0 <==> self.len() == 0, { - broadcast use {super::set::group_set_axioms, seq_to_set_is_finite}; + broadcast use super::set::group_set_lemmas; + + self.to_set_ensures(); assert(self.len() == 0 ==> self.to_set().len() == 0) by { self.lemma_cardinality_of_set() } assert(!(self.len() == 0) ==> !(self.to_set().len() == 0)) by { @@ -1103,7 +1139,10 @@ impl Seq { self.no_duplicates(), decreases self.len(), { - broadcast use {super::set::group_set_axioms, seq_to_set_is_finite}; + broadcast use super::set::group_set_lemmas; + + self.to_set_ensures(); + self.drop_first().to_set_ensures(); if self.len() == 0 { } else { @@ -1150,7 +1189,6 @@ impl Seq { /// Appending an element to a sequence and converting to set, is equal /// to converting to set and inserting it. pub broadcast proof fn lemma_to_set_insert_commutes(sq: Seq, elt: A) - requires ensures #[trigger] (sq + seq![elt]).to_set() =~= sq.to_set().insert(elt), { @@ -1928,19 +1966,12 @@ impl Seq { ensures #[trigger] self.push(elem).to_set() =~= self.to_set().insert(elem), { - broadcast use group_seq_properties; - broadcast use super::set::group_set_axioms; + broadcast use {group_seq_properties, super::set::group_set_lemmas, Seq::to_set_ensures}; let lhs = self.push(elem).to_set(); let rhs = self.to_set().insert(elem); - - assert(lhs.subset_of(rhs)); assert forall|x: A| rhs.contains(x) implies lhs.contains(x) by { lemma_seq_contains_after_push(self, elem, x); - if x == elem { - } else { - lemma_seq_contains_after_push(self, elem, x); - } } } @@ -3012,29 +3043,13 @@ spec fn seq_to_set_rec(seq: Seq) -> Set } } -// Helper function showing that the recursive definition of set_to_seq produces a finite set -proof fn seq_to_set_rec_is_finite(seq: Seq) - ensures - seq_to_set_rec(seq).finite(), - decreases seq.len(), -{ - broadcast use super::set::group_set_axioms; - - if seq.len() > 0 { - let sub_seq = seq.drop_last(); - assert(seq_to_set_rec(sub_seq).finite()) by { - seq_to_set_rec_is_finite(sub_seq); - } - } -} - // Helper function showing that the resulting set contains all elements of the sequence proof fn seq_to_set_rec_contains(seq: Seq) ensures forall|a| #[trigger] seq.contains(a) <==> seq_to_set_rec(seq).contains(a), decreases seq.len(), { - broadcast use super::set::group_set_axioms; + broadcast use super::set::group_set_lemmas; if seq.len() > 0 { assert(forall|a| #[trigger] @@ -3059,29 +3074,17 @@ proof fn seq_to_set_rec_contains(seq: Seq) proof fn seq_to_set_equal_rec(seq: Seq) ensures seq.to_set() == seq_to_set_rec(seq), + decreases seq.len(), { - broadcast use super::set::group_set_axioms; + broadcast use super::set::group_set_lemmas; - assert(forall|n| #[trigger] seq.contains(n) <==> seq_to_set_rec(seq).contains(n)) by { + seq.to_set_ensures(); + assert(forall|n| seq.contains(n) <==> #[trigger] seq_to_set_rec(seq).contains(n)) by { seq_to_set_rec_contains(seq); } - assert(forall|n| #[trigger] seq.contains(n) <==> seq.to_set().contains(n)); assert(seq.to_set() =~= seq_to_set_rec(seq)); } -/// The set obtained from a sequence is finite -pub broadcast proof fn seq_to_set_is_finite(seq: Seq) - ensures - #[trigger] seq.to_set().finite(), -{ - broadcast use super::set::group_set_axioms; - - assert(seq.to_set().finite()) by { - seq_to_set_equal_rec(seq); - seq_to_set_rec_is_finite(seq); - } -} - pub proof fn seq_to_set_distributes_over_add(s1: Seq, s2: Seq) ensures s1.to_set() + s2.to_set() =~= (s1 + s2).to_set(), @@ -3662,12 +3665,12 @@ pub broadcast group group_filter_ensures { } pub broadcast group group_seq_lib_default { + Seq::to_set_ensures, group_filter_ensures, Seq::add_empty_left, Seq::add_empty_right, Seq::push_distributes_over_add, Seq::filter_distributes_over_add, - seq_to_set_is_finite, Seq::lemma_fold_right_split, Seq::lemma_fold_left_split, } diff --git a/source/vstd/set.rs b/source/vstd/set.rs index 66d4f2ab0c..242fcafc4c 100644 --- a/source/vstd/set.rs +++ b/source/vstd/set.rs @@ -1,4 +1,6 @@ #[allow(unused_imports)] +use super::iset::*; +#[allow(unused_imports)] use super::map::*; #[allow(unused_imports)] use super::pervasive::*; @@ -7,17 +9,13 @@ use super::prelude::*; verus! { -/// `Set` is a set type for specifications. +/// `Set` is a finite set type for specifications. /// /// An object `set: Set` is a subset of the set of all values `a: A`. /// Equivalently, it can be thought of as a boolean predicate on `A`. /// -/// In general, a set might be infinite. -/// To work specifically with finite sets, see the [`self.finite()`](Set::finite) predicate. -/// /// Sets can be constructed in a few different ways: /// * [`Set::empty`] gives an empty set -/// * [`Set::full`] gives the set of all elements in `A` /// * [`Set::new`] constructs a set from a boolean predicate /// * The [`set!`] macro, to construct small sets of a fixed size /// * By manipulating an existing sequence with [`Set::union`], [`Set::intersect`], @@ -26,13 +24,75 @@ verus! { /// /// To prove that two sequences are equal, it is usually easiest to use the extensionality /// operator `=~=`. +////////////////////////////////////////////////////////////////////////////// +// Important soundness note! +// +// In this file, one can construct `Set`s directly with +// `Set::make_set`, Since we make the assumption that all Sets are +// finite, we must be careful to only allow functions in this file +// that construct `Set`s to admit a finite number of elements. +// Otherwise, one could prove that set both finite and infinite and +// introduce false. The danger of this soundness risk is encapsulated +// in the axiom `Set::axiom_is_finite`, which assumes that the set +// is finite. +// +// Outside of this file, callers only have access to `Set` +// constructors that create only finite sets. +// +// For future work, we may figure out how to have `Set` use a +// `Seq`-like representation that is inherently finite, to eliminate +// this risk. We haven't done this yet, though, because it would +// introduce the problem of multiple representations of equivalent +// sets, which creates a different problem with extensional equality. +////////////////////////////////////////////////////////////////////////////// +/// `Set` only holds finite sets, so it can be used in recursive types. +/// For instance, a type `T` can contain a `Set`. +/// +/// To prevent Verus's internal checks from rejecting such recursive +/// types, we use an artificial definition of `Set` that hides its inclusion +/// of an `ISet`. +/// +/// To make sure that proofs in this file don't take advantage of +/// this artificial structure (e.g., to prove that any two `Set`s are +/// equal), we mark this definition as `external_body`. #[verifier::ext_equal] -#[verifier::reject_recursive_types(A)] +#[verifier::external_body] +#[verifier::accept_recursive_types(A)] pub struct Set { - set: spec_fn(A) -> bool, + dummy: core::marker::PhantomData, } impl Set { + /// Generates an `ISet` with the same elements + pub uninterp spec fn to_iset(self) -> ISet; + + /// Generates a `Set` from the given `ISet`, governed by + /// `axiom_make_set`. Thanks to the axiom `axiom_make_set`, this + /// function is known to produce a `Set` with the same elements as + /// the given `ISet`, provided that `ISet` is finite. If the given + /// `ISet` is infinite, it produces an arbitrary finite set. + uninterp spec fn make_set(s: ISet) -> Set; + + /// This axiom says that `make_set` produces a `Set` with the same + /// elements as the given `ISet`, provided that `ISet` is finite. + /// (If the given `ISet` is infinite, `make_set` produces an + /// arbitrary finite set.) + broadcast axiom fn axiom_make_set(s: ISet) + requires + s.finite(), + ensures + #![trigger Self::make_set(s).to_iset()] + Self::make_set(s).to_iset() == s, + ; + + /// This axiom says that the set represented by a `Set` is always + /// finite. + broadcast axiom fn axiom_is_finite(self) + ensures + #![trigger self.to_iset().finite()] + self.to_iset().finite(), + ; + /// The "empty" set. /// /// Usage Example:
@@ -46,43 +106,74 @@ impl
Set { /// assert(forall |x: A| !Set::::empty().contains(x)); /// ``` /// Axioms around the empty set are:
- /// * [`axiom_set_empty_finite`] - /// * [`axiom_set_empty_len`]
- /// * [`axiom_set_empty`] + /// * [`lemma_set_empty_len`]
+ /// * [`lemma_set_empty`] #[rustc_diagnostic_item = "verus::vstd::set::Set::empty"] pub closed spec fn empty() -> Set
{ - Set { set: |a| false } + Self::make_set(ISet::::empty()) } - /// Set whose membership is determined by the given boolean predicate. + /// Set whose membership is determined by the given `ISet`, + /// but only if that `ISet` is finite. /// /// Usage Examples: /// ```rust - /// let set_a = Set::new(|x : nat| x < 42); - /// let set_b = Set::::new(|x| some_predicate(x)); - /// assert(forall|x| some_predicate(x) <==> set_b.contains(x)); + /// let iset_a = ISet::new(|x : nat| x < 42); + /// let option_set_b = Set::::new(iset_a); + /// assert(iset_a.finite() ==> + /// option_set_b matches Some(s) && + /// forall|x| x < 42 <==> s.contains(x)); /// ``` - pub closed spec fn new(f: spec_fn(A) -> bool) -> Set { - Set { - set: |a| - if f(a) { - true - } else { - false - }, + pub closed spec fn new_from_iset(s: ISet) -> Option> { + if s.finite() { + Some(Self::make_set(s)) + } else { + None } } + /// Set whose membership is determined by the given boolean predicate, + /// but only if the predicate produces a finite set. (If it produces an + /// infinite set, the result of this function is None.) + /// + /// Usage Examples: + /// ```rust + /// let option_set_a = Set::new(|x : nat| x < 42); + /// let option_set_b = Set::::new(|x| some_predicate(x)); + /// assert(ISet::new(|x| some_predicate(x)).finite()) ==> + /// option_set_b matches Some(s) && + /// forall|x| some_predicate(x) <==> s.contains(x)); + /// ``` + pub closed spec fn new(f: spec_fn(A) -> bool) -> Option> { + Self::new_from_iset(ISet::new(f)) + } + + /// Set whose membership is determined by the given boolean predicate, + /// assuming the predicate produces a finite set. + /// + /// Usage Examples: + /// ```rust + /// let set_a = Set::new_assuming_finite(|x : nat| x < 42); + /// let set_b = Set::::new_assuming_finite(|x| some_predicate(x)); + /// assert(forall|x| some_predicate(x) <==> set_b.contains(x)); + /// ``` + #[deprecated(note = "Set::new_assuming_finite is helpful for incremental porting of existing code to the new version of Verus supporting finite sets. But it's dangerous since it assumes the given function describes a finite set.")] + pub closed spec fn new_assuming_finite(f: spec_fn(A) -> bool) -> Set { + Self::make_set(ISet::new(f)) + } + /// The "full" set, i.e., set containing every element of type `A`. + /// Note that if `A` is infinite, then this produces None. #[rustc_diagnostic_item = "verus::vstd::set::Set::full"] - pub open spec fn full() -> Set { - Set::empty().complement() + pub open spec fn full() -> Option> { + Set::new(|a: A| true) } /// Predicate indicating if the set contains the given element. #[rustc_diagnostic_item = "verus::vstd::set::Set::contains"] - pub closed spec fn contains(self, a: A) -> bool { - (self.set)(a) + #[verifier::inline] + pub open spec fn contains(self, a: A) -> bool { + self.to_iset().contains(a) } /// Predicate indicating if the set contains the given element: supports `self has a` syntax. @@ -106,33 +197,19 @@ impl Set { /// If that element is already in the set, then an identical set is returned. #[rustc_diagnostic_item = "verus::vstd::set::Set::insert"] pub closed spec fn insert(self, a: A) -> Set { - Set { - set: |a2| - if a2 == a { - true - } else { - (self.set)(a2) - }, - } + Self::make_set(self.to_iset().insert(a)) } /// Returns a new set with the given element removed. /// If that element is already absent from the set, then an identical set is returned. #[rustc_diagnostic_item = "verus::vstd::set::Set::remove"] pub closed spec fn remove(self, a: A) -> Set { - Set { - set: |a2| - if a2 == a { - false - } else { - (self.set)(a2) - }, - } + Self::make_set(self.to_iset().remove(a)) } /// Union of two sets. pub closed spec fn union(self, s2: Set) -> Set { - Set { set: |a| (self.set)(a) || (s2.set)(a) } + Self::make_set(self.to_iset().union(s2.to_iset())) } /// `+` operator, synonymous with `union` @@ -143,7 +220,7 @@ impl Set { /// Intersection of two sets. pub closed spec fn intersect(self, s2: Set) -> Set { - Set { set: |a| (self.set)(a) && (s2.set)(a) } + Self::make_set(self.to_iset().intersect(s2.to_iset())) } /// `*` operator, synonymous with `intersect` @@ -154,7 +231,7 @@ impl Set { /// Set difference, i.e., the set of all elements in the first one but not in the second. pub closed spec fn difference(self, s2: Set) -> Set { - Set { set: |a| (self.set)(a) && !(s2.set)(a) } + Self::make_set(self.to_iset().difference(s2.to_iset())) } /// `-` operator, synonymous with `difference` @@ -164,28 +241,31 @@ impl Set { } /// Set complement (within the space of all possible elements in `A`). - pub closed spec fn complement(self) -> Set { - Set { set: |a| !(self.set)(a) } + /// Returns None if this would be an infinite set. + pub open spec fn complement(self) -> Option> { + Set::new(|a| !self.contains(a)) } /// Set of all elements in the given set which satisfy the predicate `f`. - pub open spec fn filter(self, f: spec_fn(A) -> bool) -> Set { - self.intersect(Self::new(f)) + pub closed spec fn filter(self, f: spec_fn(A) -> bool) -> Set { + Self::make_set(self.to_iset().filter(f)) } /// Returns `true` if the set is finite. - pub closed spec fn finite(self) -> bool { - exists|f: spec_fn(A) -> nat, ub: nat| - { - &&& #[trigger] trigger_finite(f, ub) - &&& surj_on(f, self) - &&& forall|a| self.contains(a) ==> f(a) < ub - } + #[deprecated(note = "Every Set is always finite, so this is always true.")] + pub open spec fn finite(self) -> bool { + true } - /// Cardinality of the set. (Only meaningful if a set is finite.) + /// Returns `true` if this set is congruent to (contains the same elements as) + /// a given ISet. + pub open spec fn congruent(self, s2: ISet) -> bool { + forall|a: A| #![all_triggers] self.contains(a) <==> s2.contains(a) + } + + /// Cardinality of the set. pub closed spec fn len(self) -> nat { - self.fold(0, |acc: nat, a| acc + 1) + self.to_iset().len() } /// Chooses an arbitrary element of the set. @@ -198,10 +278,6 @@ impl Set { choose|a: A| self.contains(a) } - /// Creates a [`Map`] whose domain is the given set. - /// The values of the map are given by `f`, a function of the keys. - pub uninterp spec fn mk_map(self, f: spec_fn(A) -> V) -> Map; - /// Returns `true` if the sets are disjoint, i.e., if their interesection is /// the empty set. pub open spec fn disjoint(self, s2: Self) -> bool { @@ -209,467 +285,143 @@ impl Set { } } -// Closures make triggering finicky but using this to trigger explicitly works well. -spec fn trigger_finite(f: spec_fn(A) -> nat, ub: nat) -> bool { - true +/// Sets `s1` and `s2` are considered equal if and only if they contain all of the same elements. +/// This has to be an axiom because `Set` is `external_body`. +pub broadcast proof fn axiom_set_ext_equal(s1: Set, s2: Set) + ensures + #[trigger] (s1 =~= s2) <==> (forall|a: A| s1.contains(a) == s2.contains(a)), +{ + admit(); } -spec fn surj_on(f: spec_fn(A) -> B, s: Set) -> bool { - forall|a1, a2| #![all_triggers] s.contains(a1) && s.contains(a2) && a1 != a2 ==> f(a1) != f(a2) +/// Sets `s1` and `s2` are considered equal if and only if they contain all of the same elements. +/// This has to be an axiom because `Set` is `external_body`. +pub broadcast proof fn axiom_set_ext_equal_deep(s1: Set, s2: Set) + ensures + #[trigger] (s1 =~~= s2) <==> s1 =~= s2, +{ + admit(); } +broadcast use super::iset::group_iset_lemmas; + pub mod fold { - //! This module defines a fold function for finite sets and proves a number of associated - //! lemmas. - //! - //! The module was ported (with some modifications) from Isabelle/HOL's finite set theory in: - //! `HOL/Finite_Set.thy` - //! That file contains the following author list: - //! - //! - //! (* Title: HOL/Finite_Set.thy - //! Author: Tobias Nipkow - //! Author: Lawrence C Paulson - //! Author: Markus Wenzel - //! Author: Jeremy Avigad - //! Author: Andrei Popescu - //! *) - //! - //! - //! The file is distributed under a 3-clause BSD license as indicated in the file `COPYRIGHT` - //! in Isabelle's root directory, which also carries the following copyright notice: - //! - //! Copyright (c) 1986-2024, - //! University of Cambridge, - //! Technische Universitaet Muenchen, - //! and contributors. use super::*; - broadcast group group_set_axioms_early { - axiom_set_empty, - axiom_set_new, - axiom_set_insert_same, - axiom_set_insert_different, - axiom_set_remove_same, - axiom_set_remove_insert, - axiom_set_remove_different, - axiom_set_union, - axiom_set_intersect, - axiom_set_difference, - axiom_set_complement, - axiom_set_ext_equal, - axiom_set_ext_equal_deep, - axiom_set_empty_finite, - axiom_set_insert_finite, - axiom_set_remove_finite, - } - - pub open spec fn is_fun_commutative(f: spec_fn(B, A) -> B) -> bool { - forall|a1, a2, b| #[trigger] f(f(b, a2), a1) == f(f(b, a1), a2) - } - - // This predicate is intended to be used like an inductive predicate, with the corresponding - // introduction, elimination and induction rules proved below. - #[verifier(opaque)] - spec fn fold_graph(z: B, f: spec_fn(B, A) -> B, s: Set, y: B, d: nat) -> bool - decreases d, - { - if s == Set::empty() { - &&& z == y - &&& d == 0 - } else { - exists|yr, a| - { - &&& #[trigger] trigger_fold_graph(yr, a) - &&& d > 0 - &&& s.remove(a).finite() - &&& s.contains(a) - &&& fold_graph(z, f, s.remove(a), yr, sub(d, 1)) - &&& y == f(yr, a) - } - } - } - - spec fn trigger_fold_graph(yr: B, a: A) -> bool { - true - } - - // Introduction rules - proof fn lemma_fold_graph_empty_intro(z: B, f: spec_fn(B, A) -> B) - ensures - fold_graph(z, f, Set::empty(), z, 0), - { - reveal(fold_graph); - } - - proof fn lemma_fold_graph_insert_intro( - z: B, - f: spec_fn(B, A) -> B, - s: Set, - y: B, - d: nat, - a: A, - ) - requires - fold_graph(z, f, s, y, d), - !s.contains(a), - ensures - fold_graph(z, f, s.insert(a), f(y, a), d + 1), - { - broadcast use group_set_axioms_early; - - reveal(fold_graph); - let _ = trigger_fold_graph(y, a); - assert(s == s.insert(a).remove(a)); - } - - // Elimination rules - proof fn lemma_fold_graph_empty_elim(z: B, f: spec_fn(B, A) -> B, y: B, d: nat) - requires - fold_graph(z, f, Set::empty(), y, d), - ensures - z == y, - d == 0, - { - reveal(fold_graph); - } - - proof fn lemma_fold_graph_insert_elim( - z: B, - f: spec_fn(B, A) -> B, - s: Set, - y: B, - d: nat, - a: A, - ) - requires - is_fun_commutative(f), - fold_graph(z, f, s.insert(a), y, d), - !s.contains(a), - ensures - d > 0, - exists|yp| y == f(yp, a) && #[trigger] fold_graph(z, f, s, yp, sub(d, 1)), - { - reveal(fold_graph); - lemma_fold_graph_insert_elim_aux(z, f, s.insert(a), y, d, a); - assert(s.insert(a).remove(a) =~= s); - let yp = choose|yp| y == f(yp, a) && #[trigger] fold_graph(z, f, s, yp, sub(d, 1)); - } - - proof fn lemma_fold_graph_insert_elim_aux( - z: B, - f: spec_fn(B, A) -> B, - s: Set, - y: B, - d: nat, - a: A, - ) - requires - is_fun_commutative(f), - fold_graph(z, f, s, y, d), - s.contains(a), - ensures - exists|yp| y == f(yp, a) && #[trigger] fold_graph(z, f, s.remove(a), yp, sub(d, 1)), - decreases d, - { - broadcast use group_set_axioms_early; - - reveal(fold_graph); - let (yr, aa): (B, A) = choose|yr, aa| - #![all_triggers] - { - &&& trigger_fold_graph(yr, a) - &&& d > 0 - &&& s.remove(aa).finite() - &&& s.contains(aa) - &&& fold_graph(z, f, s.remove(aa), yr, sub(d, 1)) - &&& y == f(yr, aa) - }; - assert(trigger_fold_graph(yr, a)); - if s.remove(aa) == Set::empty() { - } else { - if a == aa { - } else { - lemma_fold_graph_insert_elim_aux(z, f, s.remove(aa), yr, sub(d, 1), a); - let yrp = choose|yrp| - yr == f(yrp, a) && #[trigger] fold_graph( - z, - f, - s.remove(aa).remove(a), - yrp, - sub(d, 2), - ); - assert(fold_graph(z, f, s.remove(aa).insert(aa).remove(a), f(yrp, aa), sub(d, 1))) - by { - assert(s.remove(aa).remove(a) == s.remove(aa).insert(aa).remove(a).remove(aa)); - assert(trigger_fold_graph(yrp, aa)); - }; - } - } - } - - // Induction rule - proof fn lemma_fold_graph_induct( - z: B, - f: spec_fn(B, A) -> B, - s: Set, - y: B, - d: nat, - pred: spec_fn(Set, B, nat) -> bool, - ) - requires - is_fun_commutative(f), - fold_graph(z, f, s, y, d), - pred(Set::empty(), z, 0), - forall|a, s, y, d| - pred(s, y, d) && !s.contains(a) && #[trigger] fold_graph(z, f, s, y, d) ==> pred( - #[trigger] s.insert(a), - f(y, a), - d + 1, - ), - ensures - pred(s, y, d), - decreases d, - { - broadcast use group_set_axioms_early; - - reveal(fold_graph); - if s == Set::empty() { - lemma_fold_graph_empty_elim(z, f, y, d); - } else { - let a = s.choose(); - lemma_fold_graph_insert_elim(z, f, s.remove(a), y, d, a); - let yp = choose|yp| - y == f(yp, a) && #[trigger] fold_graph(z, f, s.remove(a), yp, sub(d, 1)); - lemma_fold_graph_induct(z, f, s.remove(a), yp, sub(d, 1), pred); - } - } - impl Set { /// Folds the set, applying `f` to perform the fold. The next element for the fold is chosen by /// the choose operator. /// /// Given a set `s = {x0, x1, x2, ..., xn}`, applying this function `s.fold(init, f)` /// returns `f(...f(f(init, x0), x1), ..., xn)`. - pub closed spec fn fold(self, z: B, f: spec_fn(B, A) -> B) -> B + #[verifier::inline] + pub open spec fn fold(self, z: B, f: spec_fn(B, A) -> B) -> B recommends - self.finite(), - is_fun_commutative(f), + super::super::iset::fold::is_fun_commutative(f), { - let (y, d): (B, nat) = choose|y, d| fold_graph(z, f, self, y, d); - y - } - } - - proof fn lemma_fold_graph_finite(z: B, f: spec_fn(B, A) -> B, s: Set, y: B, d: nat) - requires - is_fun_commutative(f), - fold_graph(z, f, s, y, d), - ensures - s.finite(), - { - broadcast use group_set_axioms_early; - - let pred = |s: Set, y, d| s.finite(); - lemma_fold_graph_induct(z, f, s, y, d, pred); - } - - proof fn lemma_fold_graph_deterministic( - z: B, - f: spec_fn(B, A) -> B, - s: Set, - y1: B, - y2: B, - d1: nat, - d2: nat, - ) - requires - is_fun_commutative(f), - fold_graph(z, f, s, y1, d1), - fold_graph(z, f, s, y2, d2), - ensures - y1 == y2, - d1 == d2, - { - let pred = |s: Set, y1: B, d1: nat| - forall|y2, d2| fold_graph(z, f, s, y2, d2) ==> y1 == y2 && d2 == d1; - // Base case - assert(pred(Set::empty(), z, 0)) by { - assert forall|y2, d2| fold_graph(z, f, Set::empty(), y2, d2) implies z == y2 && d2 - == 0 by { - lemma_fold_graph_empty_elim(z, f, y2, d2); - }; - }; - // Step case - assert forall|a, s, y1, d1| - pred(s, y1, d1) && !s.contains(a) && #[trigger] fold_graph( - z, - f, - s, - y1, - d1, - ) implies pred(#[trigger] s.insert(a), f(y1, a), d1 + 1) by { - assert forall|y2, d2| fold_graph(z, f, s.insert(a), y2, d2) implies f(y1, a) == y2 && d2 - == d1 + 1 by { - lemma_fold_graph_insert_elim(z, f, s, y2, d2, a); - }; - }; - lemma_fold_graph_induct(z, f, s, y2, d2, pred); - } - - proof fn lemma_fold_is_fold_graph(z: B, f: spec_fn(B, A) -> B, s: Set, y: B, d: nat) - requires - is_fun_commutative(f), - fold_graph(z, f, s, y, d), - ensures - s.fold(z, f) == y, - { - lemma_fold_graph_finite(z, f, s, y, d); - if s.fold(z, f) != y { - let (y2, d2) = choose|y2, d2| fold_graph(z, f, s, y2, d2) && y2 != y; - lemma_fold_graph_deterministic(z, f, s, y2, y, d2, d); - assert(false); + self.to_iset().fold(z, f) } } - // At this point set cardinality is not yet defined, so we can't easily give a decreasing - // measure to prove the subsequent lemma `lemma_fold_graph_exists`. Instead, we first prove - // this lemma, for which we use the upper bound of a finiteness witness as the decreasing - // measure. - pub proof fn lemma_finite_set_induct(s: Set, pred: spec_fn(Set) -> bool) - requires - s.finite(), - pred(Set::empty()), - forall|s, a| pred(s) && s.finite() && !s.contains(a) ==> #[trigger] pred(s.insert(a)), - ensures - pred(s), - { - let (f, ub) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] - trigger_finite(f, ub) && surj_on(f, s) && (forall|a| s.contains(a) ==> f(a) < ub); - lemma_finite_set_induct_aux(s, f, ub, pred); - } +} - proof fn lemma_finite_set_induct_aux( - s: Set, - f: spec_fn(A) -> nat, - ub: nat, - pred: spec_fn(Set) -> bool, - ) - requires - surj_on(f, s), - s.finite(), - forall|a| s.contains(a) ==> f(a) < ub, - pred(Set::empty()), - forall|s, a| pred(s) && s.finite() && !s.contains(a) ==> #[trigger] pred(s.insert(a)), - ensures - pred(s), - decreases ub, - { - broadcast use group_set_axioms_early; +/// The empty set contains no elements +pub broadcast proof fn lemma_set_empty(a: A) + ensures + !(#[trigger] Set::empty().contains(a)), +{ + broadcast use Set::axiom_make_set; - if s =~= Set::empty() { - } else { - let a = s.choose(); - // If `f` maps something to `ub - 1`, remap it to `f(a)` so we can decrease ub - let fp = |aa| - if f(aa) == ub - 1 { - f(a) - } else { - f(aa) - }; - lemma_finite_set_induct_aux(s.remove(a), fp, sub(ub, 1), pred); - } - } +} - proof fn lemma_fold_graph_exists(z: B, f: spec_fn(B, A) -> B, s: Set) - requires - s.finite(), - is_fun_commutative(f), - ensures - exists|y, d| fold_graph(z, f, s, y, d), - { - let pred = |s| exists|y, d| fold_graph(z, f, s, y, d); - // Base case - assert(fold_graph(z, f, Set::empty(), z, 0)) by { - lemma_fold_graph_empty_intro(z, f); - }; - // Step case - assert forall|s, a| pred(s) && s.finite() && !s.contains(a) implies #[trigger] pred( - s.insert(a), - ) by { - let (y, d): (B, nat) = choose|y, d| fold_graph(z, f, s, y, d); - lemma_fold_graph_insert_intro(z, f, s, y, d, a); - }; - lemma_finite_set_induct(s, pred); - } +/// If `Set::::new(f)` produces `Some(s)`, then `s` contains `a` +/// if and only if `f(a)` is true. +pub broadcast proof fn lemma_set_new(f: spec_fn(A) -> bool, a: A) + requires + Set::::new(f) is Some, + ensures + #[trigger] Set::::new(f).unwrap().contains(a) == f(a), +{ + broadcast use Set::axiom_make_set; - pub broadcast proof fn lemma_fold_insert(s: Set, z: B, f: spec_fn(B, A) -> B, a: A) - requires - s.finite(), - !s.contains(a), - is_fun_commutative(f), - ensures - #[trigger] s.insert(a).fold(z, f) == f(s.fold(z, f), a), - { - lemma_fold_graph_exists(z, f, s); - let (y, d): (B, nat) = choose|y, d| fold_graph(z, f, s, y, d); - lemma_fold_graph_insert_intro(z, f, s, s.fold(z, f), d, a); - lemma_fold_is_fold_graph(z, f, s.insert(a), f(s.fold(z, f), a), d + 1); - } +} - pub broadcast proof fn lemma_fold_empty(z: B, f: spec_fn(B, A) -> B) - ensures - #[trigger] Set::empty().fold(z, f) == z, - { - let (y, d): (B, nat) = choose|y, d| fold_graph(z, f, Set::empty(), y, d); - lemma_fold_graph_empty_intro(z, f); - lemma_fold_graph_empty_elim(z, f, y, d); - } +/// If `ISet::::new(f)` is finite, then `Set::::new(f)` +/// produces `Some(s)`. Useful for triggering the `lemma_set_new` +/// to show that `s` contains `a` if and only if `f(a)` is true. +pub broadcast proof fn lemma_set_new_some(f: spec_fn(A) -> bool) + requires + ISet::::new(f).finite(), + ensures + #[trigger] Set::::new(f) is Some, +{ + broadcast use Set::axiom_make_set; } -// Axioms -/// The empty set contains no elements -pub broadcast proof fn axiom_set_empty(a: A) +/// Shows that `Set::::new_assuming_finite(f)` contains `a` +/// if and only if `f(a)` is true. +#[allow(deprecated)] +pub broadcast proof fn lemma_set_new_assuming_finite(f: spec_fn(A) -> bool, a: A) ensures - !(#[trigger] Set::empty().contains(a)), + #[trigger] Set::::new_assuming_finite(f).contains(a) == f(a), { + broadcast use Set::axiom_make_set; + + assume(ISet::new(f).finite()); // This is the assumption } -/// A call to `Set::new` with the predicate `f` contains `a` if and only if `f(a)` is true. -pub broadcast proof fn axiom_set_new(f: spec_fn(A) -> bool, a: A) +/// If an iset `s` is finite, then `Set::new_from_iset(s)` has the same +/// contents as `s`. +pub broadcast proof fn lemma_set_new_from_iset(s: ISet) + requires + s.finite(), ensures - #[trigger] Set::new(f).contains(a) == f(a), + #![trigger Set::::new_from_iset(s)] + Set::::new_from_iset(s) is Some, + Set::::new_from_iset(s).unwrap().to_iset() == s, { + broadcast use Set::axiom_make_set; + + assert(ISet::new(|a: A| s.contains(a)) =~= s); } /// The result of inserting element `a` into set `s` must contains `a`. -pub broadcast proof fn axiom_set_insert_same(s: Set, a: A) +pub broadcast proof fn lemma_set_insert_same(s: Set, a: A) ensures #[trigger] s.insert(a).contains(a), { + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; + } /// If `a1` does not equal `a2`, then the result of inserting element `a2` into set `s` /// must contain `a1` if and only if the set contained `a1` before the insertion of `a2`. -pub broadcast proof fn axiom_set_insert_different(s: Set, a1: A, a2: A) +pub broadcast proof fn lemma_set_insert_different(s: Set, a1: A, a2: A) requires a1 != a2, ensures #[trigger] s.insert(a2).contains(a1) == s.contains(a1), { + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; + } /// The result of removing element `a` from set `s` must not contain `a`. -pub broadcast proof fn axiom_set_remove_same(s: Set, a: A) +pub broadcast proof fn lemma_set_remove_same(s: Set, a: A) ensures !(#[trigger] s.remove(a).contains(a)), { + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; + } /// Removing an element `a` from a set `s` and then inserting `a` back into the set` /// is equivalent to the original set `s`. -pub broadcast proof fn axiom_set_remove_insert(s: Set, a: A) +pub broadcast proof fn lemma_set_remove_insert(s: Set, a: A) requires s.contains(a), ensures @@ -680,18 +432,18 @@ pub broadcast proof fn axiom_set_remove_insert(s: Set, a: A) ) by { if a == aa { } else { - axiom_set_remove_different(s, aa, a); - axiom_set_insert_different(s.remove(a), aa, a); + lemma_set_remove_different(s, aa, a); + lemma_set_insert_different(s.remove(a), aa, a); } }; assert forall|aa| #![all_triggers] s.contains(aa) implies s.remove(a).insert(a).contains( aa, ) by { if a == aa { - axiom_set_insert_same(s.remove(a), a); + lemma_set_insert_same(s.remove(a), a); } else { - axiom_set_remove_different(s, aa, a); - axiom_set_insert_different(s.remove(a), aa, a); + lemma_set_remove_different(s, aa, a); + lemma_set_insert_different(s.remove(a), aa, a); } }; axiom_set_ext_equal(s.remove(a).insert(a), s); @@ -699,235 +451,89 @@ pub broadcast proof fn axiom_set_remove_insert(s: Set, a: A) /// If `a1` does not equal `a2`, then the result of removing element `a2` from set `s` /// must contain `a1` if and only if the set contained `a1` before the removal of `a2`. -pub broadcast proof fn axiom_set_remove_different(s: Set, a1: A, a2: A) +pub broadcast proof fn lemma_set_remove_different(s: Set, a1: A, a2: A) requires a1 != a2, ensures #[trigger] s.remove(a2).contains(a1) == s.contains(a1), { + broadcast use axiom_set_ext_equal; + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; + } /// The union of sets `s1` and `s2` contains element `a` if and only if /// `s1` contains `a` and/or `s2` contains `a`. -pub broadcast proof fn axiom_set_union(s1: Set, s2: Set, a: A) +pub broadcast proof fn lemma_set_union(s1: Set, s2: Set, a: A) ensures #[trigger] s1.union(s2).contains(a) == (s1.contains(a) || s2.contains(a)), { + broadcast use axiom_set_ext_equal; + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; + } /// The intersection of sets `s1` and `s2` contains element `a` if and only if /// both `s1` and `s2` contain `a`. -pub broadcast proof fn axiom_set_intersect(s1: Set, s2: Set, a: A) +pub broadcast proof fn lemma_set_intersect(s1: Set, s2: Set, a: A) ensures #[trigger] s1.intersect(s2).contains(a) == (s1.contains(a) && s2.contains(a)), { + broadcast use axiom_set_ext_equal; + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; + } /// The set difference between `s1` and `s2` contains element `a` if and only if /// `s1` contains `a` and `s2` does not contain `a`. -pub broadcast proof fn axiom_set_difference(s1: Set, s2: Set, a: A) +pub broadcast proof fn lemma_set_difference(s1: Set, s2: Set, a: A) ensures #[trigger] s1.difference(s2).contains(a) == (s1.contains(a) && !s2.contains(a)), { -} - -/// The complement of set `s` contains element `a` if and only if `s` does not contain `a`. -pub broadcast proof fn axiom_set_complement(s: Set, a: A) - ensures - #[trigger] s.complement().contains(a) == !s.contains(a), -{ -} - -/// Sets `s1` and `s2` are equal if and only if they contain all of the same elements. -pub broadcast proof fn axiom_set_ext_equal(s1: Set, s2: Set) - ensures - #[trigger] (s1 =~= s2) <==> (forall|a: A| s1.contains(a) == s2.contains(a)), -{ - if s1 =~= s2 { - assert(forall|a: A| s1.contains(a) == s2.contains(a)); - } - if forall|a: A| s1.contains(a) == s2.contains(a) { - if !(forall|a: A| #[trigger] (s1.set)(a) <==> (s2.set)(a)) { - assert(exists|a: A| #[trigger] (s1.set)(a) != (s2.set)(a)); - let a = choose|a: A| #[trigger] (s1.set)(a) != (s2.set)(a); - assert(s1.contains(a)); - assert(false); - } - assert(s1 =~= s2); - } -} + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; -pub broadcast proof fn axiom_set_ext_equal_deep(s1: Set, s2: Set) - ensures - #[trigger] (s1 =~~= s2) <==> s1 =~= s2, -{ } -pub broadcast axiom fn axiom_mk_map_domain(s: Set, f: spec_fn(K) -> V) - ensures - #[trigger] s.mk_map(f).dom() == s, -; - -pub broadcast axiom fn axiom_mk_map_index(s: Set, f: spec_fn(K) -> V, key: K) - requires - s.contains(key), - ensures - #[trigger] s.mk_map(f)[key] == f(key), -; - -// Trusted axioms about finite -/// The empty set is finite. -pub broadcast proof fn axiom_set_empty_finite() - ensures - #[trigger] Set::::empty().finite(), -{ - let f = |a: A| 0; - let ub = 0; - let _ = trigger_finite(f, ub); -} - -/// The result of inserting an element `a` into a finite set `s` is also finite. -pub broadcast proof fn axiom_set_insert_finite(s: Set, a: A) - requires - s.finite(), - ensures - #[trigger] s.insert(a).finite(), -{ - let (f, ub) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] - trigger_finite(f, ub) && surj_on(f, s) && (forall|a| s.contains(a) ==> f(a) < ub); - let f2 = |a2: A| - if a2 == a { - ub - } else { - f(a2) - }; - let ub2 = ub + 1; - let _ = trigger_finite(f2, ub2); - assert forall|a1, a2| - #![all_triggers] - s.insert(a).contains(a1) && s.insert(a).contains(a2) && a1 != a2 implies f2(a1) != f2( - a2, - ) by { - if a != a1 { - assert(s.contains(a1)); - } - if a != a2 { - assert(s.contains(a2)); - } - }; - assert forall|a2| s.insert(a).contains(a2) implies #[trigger] f2(a2) < ub2 by { - if a == a2 { - } else { - assert(s.contains(a2)); - } - }; -} - -/// The result of removing an element `a` from a finite set `s` is also finite. -pub broadcast proof fn axiom_set_remove_finite(s: Set, a: A) - requires - s.finite(), - ensures - #[trigger] s.remove(a).finite(), -{ - let (f, ub) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] - trigger_finite(f, ub) && surj_on(f, s) && (forall|a| s.contains(a) ==> f(a) < ub); - assert forall|a1, a2| - #![all_triggers] - s.remove(a).contains(a1) && s.remove(a).contains(a2) && a1 != a2 implies f(a1) != f(a2) by { - if a != a1 { - assert(s.contains(a1)); - } - if a != a2 { - assert(s.contains(a2)); - } - }; - assert(surj_on(f, s.remove(a))); - assert forall|a2| s.remove(a).contains(a2) implies #[trigger] f(a2) < ub by { - if a == a2 { - } else { - assert(s.contains(a2)); - } - }; -} - -/// The union of two finite sets is finite. -pub broadcast proof fn axiom_set_union_finite(s1: Set, s2: Set) +/// The complement of set `s` contains element `a` if and only if `s` does not contain `a`. +pub broadcast proof fn lemma_set_complement(s: Set, a: A) requires - s1.finite(), - s2.finite(), + ISet::new(|a| !s.contains(a)).finite(), ensures - #[trigger] s1.union(s2).finite(), + #[trigger] s.complement().unwrap().contains(a) == !s.contains(a), { - let (f1, ub1) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] - trigger_finite(f, ub) && surj_on(f, s1) && (forall|a| s1.contains(a) ==> f(a) < ub); - let (f2, ub2) = choose|f: spec_fn(A) -> nat, ub: nat| #[trigger] - trigger_finite(f, ub) && surj_on(f, s2) && (forall|a| s2.contains(a) ==> f(a) < ub); - let f3 = |a| - if s1.contains(a) { - f1(a) - } else { - ub1 + f2(a) - }; - let ub3 = ub1 + ub2; - assert(trigger_finite(f3, ub3)); - assert(forall|a| - #![all_triggers] - s1.union(s2).contains(a) ==> s1.contains(a) || s2.contains(a)); -} + broadcast use Set::axiom_make_set; -/// The intersection of two finite sets is finite. -pub broadcast proof fn axiom_set_intersect_finite(s1: Set, s2: Set) - requires - s1.finite() || s2.finite(), - ensures - #[trigger] s1.intersect(s2).finite(), -{ - assert(forall|a| - #![all_triggers] - s1.intersect(s2).contains(a) ==> s1.contains(a) && s2.contains(a)); } -/// The set difference between two finite sets is finite. -pub broadcast proof fn axiom_set_difference_finite(s1: Set, s2: Set) - requires - s1.finite(), +/// The filter of set `s` using function `f` contains element `a` if and only if `s` contains `a` +/// and `f(a)` is true. +pub broadcast proof fn lemma_set_filter(s: Set, f: spec_fn(A) -> bool, a: A) ensures - #[trigger] s1.difference(s2).finite(), + #[trigger] s.filter(f).contains(a) == (s.contains(a) && f(a)), { - assert(forall|a| - #![all_triggers] - s1.difference(s2).contains(a) ==> s1.contains(a) && !s2.contains(a)); -} + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; -/// An infinite set `s` contains the element `s.choose()`. -pub broadcast proof fn axiom_set_choose_infinite(s: Set) - requires - !s.finite(), - ensures - #[trigger] s.contains(s.choose()), -{ - let f = |a: A| 0; - let ub = 0; - let _ = trigger_finite(f, ub); } -// Trusted axioms about len -// Note: we could add more axioms about len, but they would be incomplete. -// The following, with axiom_set_ext_equal, are enough to build libraries about len. +// Lemmas about len +// The following, with lemma_set_ext_equal, are enough to build libraries about len. /// The empty set has length 0. -pub broadcast proof fn axiom_set_empty_len() +pub broadcast proof fn lemma_set_empty_len() ensures #[trigger] Set::::empty().len() == 0, { - fold::lemma_fold_empty(0, |b: nat, a: A| b + 1); + broadcast use Set::axiom_make_set; + } /// The result of inserting an element `a` into a finite set `s` has length /// `s.len() + 1` if `a` is not already in `s` and length `s.len()` otherwise. -pub broadcast proof fn axiom_set_insert_len(s: Set, a: A) - requires - s.finite(), +pub broadcast proof fn lemma_set_insert_len(s: Set, a: A) ensures #[trigger] s.insert(a).len() == s.len() + (if s.contains(a) { 0int @@ -935,18 +541,14 @@ pub broadcast proof fn axiom_set_insert_len(s: Set, a: A) 1 }), { - if s.contains(a) { - assert(s =~= s.insert(a)); - } else { - fold::lemma_fold_insert(s, 0, |b: nat, a: A| b + 1, a); - } + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; + } /// The result of removing an element `a` from a finite set `s` has length /// `s.len() - 1` if `a` is in `s` and length `s.len()` otherwise. -pub broadcast proof fn axiom_set_remove_len(s: Set, a: A) - requires - s.finite(), +pub broadcast proof fn lemma_set_remove_len(s: Set, a: A) ensures s.len() == #[trigger] s.remove(a).len() + (if s.contains(a) { 1int @@ -954,76 +556,73 @@ pub broadcast proof fn axiom_set_remove_len(s: Set, a: A) 0 }), { - axiom_set_remove_finite(s, a); - axiom_set_insert_len(s.remove(a), a); - if s.contains(a) { - assert(s =~= s.remove(a).insert(a)); - } else { - assert(s =~= s.remove(a)); - } + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; + } /// If a finite set `s` contains any element, it has length greater than 0. -pub broadcast proof fn axiom_set_contains_len(s: Set, a: A) +pub broadcast proof fn lemma_set_contains_len(s: Set, a: A) requires - s.finite(), #[trigger] s.contains(a), ensures #[trigger] s.len() != 0, { - let a = s.choose(); - assert(s.remove(a).insert(a) =~= s); - axiom_set_remove_finite(s, a); - axiom_set_insert_finite(s.remove(a), a); - axiom_set_insert_len(s.remove(a), a); + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; + } /// A finite set `s` contains the element `s.choose()` if it has length greater than 0. -pub broadcast proof fn axiom_set_choose_len(s: Set) +pub broadcast proof fn lemma_set_choose_len(s: Set) requires - s.finite(), #[trigger] s.len() != 0, ensures #[trigger] s.contains(s.choose()), { - // Separate statements to work around https://github.com/verus-lang/verusfmt/issues/86 - broadcast use axiom_set_contains_len; - broadcast use axiom_set_empty_len; - broadcast use axiom_set_ext_equal; - broadcast use axiom_set_insert_finite; + assert(s.to_iset().contains(s.to_iset().choose())); +} + +/// Converting a `Set` to an `ISet` produces a finite result with the same contents +/// and length. That is, `contains` and `len` produce the same results. +pub broadcast proof fn lemma_to_iset(s: Set) + ensures + #![trigger s.to_iset()] + s.to_iset().finite(), + s.to_iset().len() == s.len(), + forall|a: A| #[trigger] s.to_iset().contains(a) <==> s.contains(a), + decreases s.len(), +{ + broadcast use Set::axiom_make_set; + broadcast use Set::axiom_is_finite; - let pred = |s: Set| s.finite() ==> s.len() == 0 <==> s =~= Set::empty(); - fold::lemma_finite_set_induct(s, pred); } -pub broadcast group group_set_axioms { - axiom_set_empty, - axiom_set_new, - axiom_set_insert_same, - axiom_set_insert_different, - axiom_set_remove_same, - axiom_set_remove_insert, - axiom_set_remove_different, - axiom_set_union, - axiom_set_intersect, - axiom_set_difference, - axiom_set_complement, +pub broadcast group group_set_lemmas { axiom_set_ext_equal, axiom_set_ext_equal_deep, - axiom_mk_map_domain, - axiom_mk_map_index, - axiom_set_empty_finite, - axiom_set_insert_finite, - axiom_set_remove_finite, - axiom_set_union_finite, - axiom_set_intersect_finite, - axiom_set_difference_finite, - axiom_set_choose_infinite, - axiom_set_empty_len, - axiom_set_insert_len, - axiom_set_remove_len, - axiom_set_contains_len, - axiom_set_choose_len, + lemma_set_empty, + lemma_set_new, + lemma_set_new_assuming_finite, + lemma_set_new_from_iset, + lemma_set_new_some, + lemma_set_insert_same, + lemma_set_insert_different, + lemma_set_remove_same, + lemma_set_remove_insert, + lemma_set_remove_different, + lemma_set_union, + lemma_set_intersect, + lemma_set_difference, + lemma_set_complement, + lemma_set_filter, + lemma_set_empty_len, + lemma_set_insert_len, + lemma_set_remove_len, + lemma_set_contains_len, + lemma_set_choose_len, + lemma_set_new, + lemma_to_iset, } // Macros diff --git a/source/vstd/set_lib.rs b/source/vstd/set_lib.rs index 7f19ea4fec..1d9a276aca 100644 --- a/source/vstd/set_lib.rs +++ b/source/vstd/set_lib.rs @@ -1,4 +1,6 @@ #[allow(unused_imports)] +use super::iset::*; +#[allow(unused_imports)] use super::multiset::Multiset; #[allow(unused_imports)] use super::pervasive::*; @@ -12,22 +14,26 @@ use super::set::*; verus! { -broadcast use super::set::group_set_axioms; +broadcast use {super::iset::group_iset_lemmas, super::set::group_set_lemmas}; impl Set { - /// Is `true` if called by a "full" set, i.e., a set containing every element of type `A`. - pub open spec fn is_full(self) -> bool { - self == Set::::full() - } - /// Is `true` if called by an "empty" set, i.e., a set containing no elements and has length 0 pub open spec fn is_empty(self) -> (b: bool) { self =~= Set::::empty() } /// Returns the set contains an element `f(x)` for every element `x` in `self`. - pub open spec fn map(self, f: spec_fn(A) -> B) -> Set { - Set::new(|a: B| exists|x: A| self.contains(x) && a == f(x)) + pub closed spec fn map(self, f: spec_fn(A) -> B) -> Set { + Set::new_from_iset(self.to_iset().map(f)).unwrap() + } + + /// Since `Self::map` is `closed`, this broadcast lemma is needed + /// to make its semantics visible to the verifier. + pub broadcast proof fn lemma_map_contains(self, f: spec_fn(A) -> B, b: B) + ensures + #[trigger] self.map(f).contains(b) <==> exists|a: A| self.contains(a) && b == f(a), + { + self.to_iset().lemma_map_finite(f); } /// `Set::map_by` is like `Set::map`, but `map` only takes a forward function `fwd: spec_fn(A) -> B`, @@ -42,11 +48,28 @@ impl Set { /// If the recommendation `forall|a: A| self.contains(a) ==> rev(fwd(a)) == a` is satisfied, /// it is trivially guaranteed that `self.map_by(fwd, rev) == self.map(fwd)`. /// Also see the `set_build!` macro for a convenient interface to `map_by`. - pub open spec fn map_by(self, fwd: spec_fn(A) -> B, rev: spec_fn(B) -> A) -> Set + pub closed spec fn map_by(self, fwd: spec_fn(A) -> B, rev: spec_fn(B) -> A) -> Set recommends forall|a: A| self.contains(a) ==> rev(fwd(a)) == a, { - Set::new(|b: B| self.contains(rev(b)) && b == fwd(rev(b))) + Set::new_from_iset(self.to_iset().map_by(fwd, rev)).unwrap() + } + + /// Since `Self::map_by` is `closed`, this broadcast lemma is needed + /// to make its semantics visible to the verifier. + pub broadcast proof fn lemma_map_by_contains( + self, + fwd: spec_fn(A) -> B, + rev: spec_fn(B) -> A, + b: B, + ) + ensures + #[trigger] self.map_by(fwd, rev).contains(b) <==> self.contains(rev(b)) && b == fwd( + rev(b), + ), + decreases self.len(), + { + self.to_iset().lemma_map_by_finite(fwd, rev); } /// Similar to `Set::map_by`, but the forward function returns `Set` rather than `B`, @@ -54,7 +77,7 @@ impl Set { /// This can be easier to work with in proofs than calling `map` and `flatten` separately, /// since `map` and `flatten` introduce "exists", while `map_flatten_by` does not. /// Also see the `set_build!` macro for a convenient interface to `map_flatten_by`. - pub open spec fn map_flatten_by( + pub closed spec fn map_flatten_by( self, fwd: spec_fn(A) -> Set, rev: spec_fn(B) -> A, @@ -63,9 +86,54 @@ impl Set { forall|a: A, b: B| #[trigger] self.contains(a) && fwd(a).contains(b) ==> #[trigger] rev(b) == a, { - Set::new(|b: B| self.contains(rev(b)) && fwd(rev(b)).contains(b)) + Set::new(|b: B| self.contains(rev(b)) && fwd(rev(b)).contains(b)).unwrap() + } + + /// This helper lemma demonstrates that the result of calling `Self::map_by` produces + /// a finite, and thus valid, `Set`. + proof fn lemma_map_flatten_by_finite(self, fwd: spec_fn(A) -> Set, rev: spec_fn(B) -> A) + requires + forall|a: A, b: B| #[trigger] + self.contains(a) && fwd(a).contains(b) ==> #[trigger] rev(b) == a, + ensures + ISet::new(|b: B| self.contains(rev(b)) && fwd(rev(b)).contains(b)).finite(), + decreases self.len(), + { + let map_f = |b: B| self.contains(rev(b)) && fwd(rev(b)).contains(b); + if self == Self::empty() { + assert(ISet::::new(map_f) =~= ISet::::empty()); + } else { + lemma_set_is_empty(self); + let a: A = choose|a: A| self.contains(a); + self.remove(a).lemma_map_flatten_by_finite(fwd, rev); + let map_remove_f = |b: B| self.remove(a).contains(rev(b)) && fwd(rev(b)).contains(b); + assert(ISet::::new(map_f) =~= ISet::::new(map_remove_f).union(fwd(a).to_iset())); + lemma_to_iset(fwd(a)); + } + } + + /// Since `Self::map_flatten_by` is `closed`, this broadcast lemma is needed + /// to make its semantics visible to the verifier. + pub broadcast proof fn lemma_map_flatten_by_contains( + self, + fwd: spec_fn(A) -> Set, + rev: spec_fn(B) -> A, + b: B, + ) + requires + forall|a: A, b: B| #[trigger] + self.contains(a) && fwd(a).contains(b) ==> #[trigger] rev(b) == a, + ensures + #[trigger] self.map_flatten_by(fwd, rev).contains(b) <==> self.contains(rev(b)) && fwd( + rev(b), + ).contains(b), + decreases self.len(), + { + self.lemma_map_flatten_by_finite(fwd, rev); } + /// This proof demonstrates that calling `map_flatten_by` is equivalent to + /// first calling `map`, then calling `flatten`. pub proof fn map_flatten_by_is_map_flatten( self, fwd: spec_fn(A) -> Set, @@ -77,21 +145,23 @@ impl Set { ensures self.map_flatten_by(fwd, rev) == self.map(fwd).flatten(), { + broadcast use Set::lemma_flatten_contains; + broadcast use Set::lemma_map_flatten_by_contains; + broadcast use Set::lemma_map_contains; + + self.lemma_map_flatten_by_finite(fwd, rev); assert forall|b: B| self.map_flatten_by(fwd, rev).contains(b) implies #[trigger] self.map( fwd, ).flatten().contains(b) by { let bs = choose|bs: Set| - (exists|a: A| self.contains(a) && bs == fwd(a)) && bs.contains(b); + (exists|a: A| self.contains(a) && bs == fwd(a)) && #[trigger] bs.contains(b); assert(self.map(fwd).contains(bs) <==> (exists|a: A| self.contains(a) && bs == fwd(a))); } } /// Converts a set into a sequence with an arbitrary ordering. pub open spec fn to_seq(self) -> Seq - recommends - self.finite(), decreases self.len(), - when self.finite() { if self.len() == 0 { Seq::::empty() @@ -112,86 +182,67 @@ impl Set { &&& (forall|x: A, y: A| self.contains(x) && self.contains(y) ==> x == y) } + /// Indicates if the function given by `r` is injective on this set, + /// i.e., whether each element of this set is mapped to a different + /// value by `r`. + pub open spec fn injective_on(self, r: spec_fn(A) -> B) -> bool { + self.to_iset().injective_on(r) + } + + /// An element in an ordered set is called a least element (or a minimum), if it is less than + /// every other element of the set. + pub open spec fn has_least(self, leq: spec_fn(A, A) -> bool, min: A) -> bool { + self.to_iset().has_least(leq, min) + } + + /// An element in an ordered set is called a minimal element, if no other element is less than it. + pub open spec fn has_minimum(self, leq: spec_fn(A, A) -> bool, min: A) -> bool { + self.to_iset().has_minimum(leq, min) + } + + /// An element in an ordered set is called a greatest element (or a maximum), if it is greater than + ///every other element of the set. + pub open spec fn has_greatest(self, leq: spec_fn(A, A) -> bool, max: A) -> bool { + self.to_iset().has_greatest(leq, max) + } + + /// An element in an ordered set is called a maximal element, if no other element is greater than it. + pub open spec fn has_maximum(self, leq: spec_fn(A, A) -> bool, max: A) -> bool { + self.to_iset().has_maximum(leq, max) + } + + /// If a function is injective on the set `self`, then it is also injective on any subset `other` of `self`. + pub proof fn lemma_injective_on_subset(self, r: spec_fn(A) -> B, other: Self) + requires + other <= self, + self.injective_on(r), + ensures + other.injective_on(r), + { + self.to_iset().lemma_injective_on_subset(r, other.to_iset()); + } + /// Any totally-ordered set contains a unique minimal (equivalently, least) element. /// Returns an arbitrary value if r is not a total ordering pub closed spec fn find_unique_minimal(self, r: spec_fn(A, A) -> bool) -> A recommends total_ordering(r), self.len() > 0, - self.finite(), decreases self.len(), - when self.finite() { - proof { - broadcast use group_set_properties; - - } - if self.len() <= 1 { - self.choose() - } else { - let x = choose|x: A| self.contains(x); - let min = self.remove(x).find_unique_minimal(r); - if r(min, x) { - min - } else { - x - } - } + self.to_iset().find_unique_minimal(r) } /// Proof of correctness and expected behavior for `Set::find_unique_minimal`. pub proof fn find_unique_minimal_ensures(self, r: spec_fn(A, A) -> bool) requires - self.finite(), self.len() > 0, total_ordering(r), ensures - is_minimal(r, self.find_unique_minimal(r), self) && (forall|min: A| - is_minimal(r, min, self) ==> self.find_unique_minimal(r) == min), - decreases self.len(), + self.has_minimum(r, self.find_unique_minimal(r)) && (forall|min: A| + self.has_minimum(r, min) ==> self.find_unique_minimal(r) == min), { - broadcast use group_set_properties; - - if self.len() == 1 { - let x = choose|x: A| self.contains(x); - assert(self.remove(x).insert(x) =~= self); - assert(is_minimal(r, self.find_unique_minimal(r), self)); - } else { - let x = choose|x: A| self.contains(x); - self.remove(x).find_unique_minimal_ensures(r); - assert(is_minimal(r, self.remove(x).find_unique_minimal(r), self.remove(x))); - let y = self.remove(x).find_unique_minimal(r); - let min_updated = self.find_unique_minimal(r); - assert(!r(y, x) ==> min_updated == x); - assert(forall|elt: A| - self.remove(x).contains(elt) && #[trigger] r(elt, y) ==> #[trigger] r(y, elt)); - assert forall|elt: A| - self.contains(elt) && #[trigger] r(elt, min_updated) implies #[trigger] r( - min_updated, - elt, - ) by { - assert(r(min_updated, x) || r(min_updated, y)); - if min_updated == y { // Case where the new min is the old min - assert(is_minimal(r, self.find_unique_minimal(r), self)); - } else { //Case where the new min is the newest element - assert(self.remove(x).contains(elt) || elt == x); - assert(min_updated == x); - assert(r(x, y) || r(y, x)); - assert(!r(x, y) || !r(y, x)); - assert(!(min_updated == y) ==> !r(y, x)); - assert(r(x, y)); - if (self.remove(x).contains(elt)) { - assert(r(elt, y) && r(y, elt) ==> elt == y); - } else { - } - } - } - assert forall|min_poss: A| - is_minimal(r, min_poss, self) implies self.find_unique_minimal(r) == min_poss by { - assert(is_minimal(r, min_poss, self.remove(x)) || x == min_poss); - assert(r(min_poss, self.find_unique_minimal(r))); - } - } + self.to_iset().find_unique_minimal_ensures(r); } /// Any totally-ordered set contains a unique maximal (equivalently, greatest) element. @@ -200,92 +251,26 @@ impl Set { recommends total_ordering(r), self.len() > 0, - decreases self.len(), - when self.finite() { - proof { - broadcast use group_set_properties; - - } - if self.len() <= 1 { - self.choose() - } else { - let x = choose|x: A| self.contains(x); - let max = self.remove(x).find_unique_maximal(r); - if r(x, max) { - max - } else { - x - } - } + self.to_iset().find_unique_maximal(r) } /// Proof of correctness and expected behavior for `Set::find_unique_maximal`. pub proof fn find_unique_maximal_ensures(self, r: spec_fn(A, A) -> bool) requires - self.finite(), self.len() > 0, total_ordering(r), ensures - is_maximal(r, self.find_unique_maximal(r), self) && (forall|max: A| - is_maximal(r, max, self) ==> self.find_unique_maximal(r) == max), - decreases self.len(), + self.has_maximum(r, self.find_unique_maximal(r)) && (forall|max: A| + self.has_maximum(r, max) ==> self.find_unique_maximal(r) == max), { - broadcast use group_set_properties; - - if self.len() == 1 { - let x = choose|x: A| self.contains(x); - assert(self.remove(x) =~= Set::::empty()); - assert(self.contains(self.find_unique_maximal(r))); - } else { - let x = choose|x: A| self.contains(x); - self.remove(x).find_unique_maximal_ensures(r); - assert(is_maximal(r, self.remove(x).find_unique_maximal(r), self.remove(x))); - assert(self.remove(x).insert(x) =~= self); - let y = self.remove(x).find_unique_maximal(r); - let max_updated = self.find_unique_maximal(r); - assert(max_updated == x || max_updated == y); - assert(!r(x, y) ==> max_updated == x); - assert forall|elt: A| - self.contains(elt) && #[trigger] r(max_updated, elt) implies #[trigger] r( - elt, - max_updated, - ) by { - assert(r(x, max_updated) || r(y, max_updated)); - if max_updated == y { // Case where the new max is the old max - assert(r(elt, max_updated)); - assert(r(x, max_updated)); - assert(is_maximal(r, self.find_unique_maximal(r), self)); - } else { //Case where the new max is the newest element - assert(self.remove(x).contains(elt) || elt == x); - assert(max_updated == x); - assert(r(x, y) || r(y, x)); - assert(!r(x, y) || !r(y, x)); - assert(!(max_updated == y) ==> !r(x, y)); - assert(r(y, x)); - if (self.remove(x).contains(elt)) { - assert(r(y, elt) ==> r(elt, y)); - assert(r(y, elt) && r(elt, y) ==> elt == y); - assert(r(elt, x)); - assert(r(elt, max_updated)) - } else { - } - } - } - assert forall|max_poss: A| - is_maximal(r, max_poss, self) implies self.find_unique_maximal(r) == max_poss by { - assert(is_maximal(r, max_poss, self.remove(x)) || x == max_poss); - assert(r(max_poss, self.find_unique_maximal(r))); - assert(r(self.find_unique_maximal(r), max_poss)); - } - } + self.to_iset().find_unique_maximal_ensures(r); } /// Converts a set into a multiset where each element from the set has /// multiplicity 1 and any other element has multiplicity 0. pub open spec fn to_multiset(self) -> Multiset decreases self.len(), - when self.finite() { if self.len() == 0 { Multiset::::empty() @@ -299,7 +284,6 @@ impl Set { /// A finite set with length 0 is equivalent to the empty set. pub proof fn lemma_len0_is_empty(self) requires - self.finite(), self.len() == 0, ensures self == Set::::empty(), @@ -325,8 +309,6 @@ impl Set { /// A set has exactly one element, if and only if, it has at least one element and any two elements are equal. pub proof fn lemma_is_singleton(s: Set) - requires - s.finite(), ensures s.is_singleton() == (s.len() == 1), { @@ -346,14 +328,17 @@ impl Set { /// The result of filtering a finite set is finite and has size less than or equal to the original set. pub proof fn lemma_len_filter(self, f: spec_fn(A) -> bool) - requires - self.finite(), ensures - self.filter(f).finite(), self.filter(f).len() <= self.len(), decreases self.len(), { - lemma_len_intersect::(self, Set::new(f)); + if self.is_empty() { + assert(self.filter(f) =~= self); + } else { + let a = self.choose(); + assert(self.filter(f).remove(a) =~= self.remove(a).filter(f)); + self.remove(a).lemma_len_filter(f); + } } /// In a pre-ordered set, a greatest element is necessarily maximal. @@ -361,7 +346,7 @@ impl Set { requires pre_ordering(r), ensures - is_greatest(r, max, self) ==> is_maximal(r, max, self), + self.has_greatest(r, max) ==> self.has_maximum(r, max), { } @@ -370,7 +355,7 @@ impl Set { requires pre_ordering(r), ensures - is_least(r, min, self) ==> is_minimal(r, min, self), + self.has_least(r, min) ==> self.has_minimum(r, min), { } @@ -379,10 +364,9 @@ impl Set { requires total_ordering(r), ensures - is_greatest(r, max, self) <==> is_maximal(r, max, self), + self.has_greatest(r, max) <==> self.has_maximum(r, max), { - assert(is_maximal(r, max, self) ==> forall|x: A| - !self.contains(x) || !r(max, x) || r(x, max)); + self.to_iset().lemma_maximal_equivalent_greatest(r, max); } /// In a totally-ordered set, an element is maximal if and only if it is a greatest element. @@ -390,10 +374,9 @@ impl Set { requires total_ordering(r), ensures - is_least(r, min, self) <==> is_minimal(r, min, self), + self.has_least(r, min) <==> self.has_minimum(r, min), { - assert(is_minimal(r, min, self) ==> forall|x: A| - !self.contains(x) || !r(x, min) || r(min, x)); + self.to_iset().lemma_minimal_equivalent_least(r, min); } /// In a partially-ordered set, there exists at most one least element. @@ -402,13 +385,9 @@ impl Set { partial_ordering(r), ensures forall|min: A, min_prime: A| - is_least(r, min, self) && is_least(r, min_prime, self) ==> min == min_prime, + self.has_least(r, min) && self.has_least(r, min_prime) ==> min == min_prime, { - assert forall|min: A, min_prime: A| - is_least(r, min, self) && is_least(r, min_prime, self) implies min == min_prime by { - assert(r(min, min_prime)); - assert(r(min_prime, min)); - } + self.to_iset().lemma_least_is_unique(r); } /// In a partially-ordered set, there exists at most one greatest element. @@ -417,14 +396,9 @@ impl Set { partial_ordering(r), ensures forall|max: A, max_prime: A| - is_greatest(r, max, self) && is_greatest(r, max_prime, self) ==> max == max_prime, + self.has_greatest(r, max) && self.has_greatest(r, max_prime) ==> max == max_prime, { - assert forall|max: A, max_prime: A| - is_greatest(r, max, self) && is_greatest(r, max_prime, self) implies max - == max_prime by { - assert(r(max_prime, max)); - assert(r(max, max_prime)); - } + self.to_iset().lemma_greatest_is_unique(r); } /// In a totally-ordered set, there exists at most one minimal element. @@ -433,31 +407,20 @@ impl Set { total_ordering(r), ensures forall|min: A, min_prime: A| - is_minimal(r, min, self) && is_minimal(r, min_prime, self) ==> min == min_prime, + self.has_minimum(r, min) && self.has_minimum(r, min_prime) ==> min == min_prime, { - assert forall|min: A, min_prime: A| - is_minimal(r, min, self) && is_minimal(r, min_prime, self) implies min == min_prime by { - self.lemma_minimal_equivalent_least(r, min); - self.lemma_minimal_equivalent_least(r, min_prime); - self.lemma_least_is_unique(r); - } + self.to_iset().lemma_minimal_is_unique(r); } /// In a totally-ordered set, there exists at most one maximal element. pub proof fn lemma_maximal_is_unique(self, r: spec_fn(A, A) -> bool) requires - self.finite(), total_ordering(r), ensures forall|max: A, max_prime: A| - is_maximal(r, max, self) && is_maximal(r, max_prime, self) ==> max == max_prime, + self.has_maximum(r, max) && self.has_maximum(r, max_prime) ==> max == max_prime, { - assert forall|max: A, max_prime: A| - is_maximal(r, max, self) && is_maximal(r, max_prime, self) implies max == max_prime by { - self.lemma_maximal_equivalent_greatest(r, max); - self.lemma_maximal_equivalent_greatest(r, max_prime); - self.lemma_greatest_is_unique(r); - } + self.to_iset().lemma_maximal_is_unique(r); } /// Set difference with an additional element inserted decreases the size of @@ -467,7 +430,6 @@ impl Set { requires self.contains(elt), !s.contains(elt), - self.finite(), ensures #[trigger] self.difference(s.insert(elt)).len() < self.difference(s).len(), { @@ -478,7 +440,6 @@ impl Set { pub proof fn lemma_subset_not_in_lt(self: Set, s2: Set, elt: A) requires self.subset_of(s2), - s2.finite(), !self.contains(elt), s2.contains(elt), ensures @@ -495,6 +456,8 @@ impl Set { ensures #[trigger] self.insert(elt).map(f) =~= self.map(f).insert(f(elt)), { + broadcast use Set::lemma_map_contains; + assert forall|x: B| self.map(f).insert(f(elt)).contains(x) implies self.insert(elt).map( f, ).contains(x) by { @@ -512,7 +475,7 @@ impl Set { ensures (self.union(t)).map(f) =~= self.map(f).union(t.map(f)), { - broadcast use group_set_axioms; + broadcast use Set::lemma_map_contains; let lhs = self.union(t).map(f); let rhs = self.map(f).union(t.map(f)); @@ -552,6 +515,8 @@ impl Set { ensures #[trigger] self.map(f).any(q), { + broadcast use Set::lemma_map_contains; + let x = choose|x: A| self.contains(x) && p(x); assert(self.map(f).contains(f(x))); } @@ -579,7 +544,9 @@ impl Set { None => s.filter_map(f), }), { - broadcast use group_set_axioms; + broadcast use group_set_lemmas; + broadcast use Set::lemma_flatten_contains; + broadcast use Set::lemma_map_contains; broadcast use Set::lemma_set_map_insert_commute; let lhs = s.insert(elem).filter_map(f); @@ -617,7 +584,9 @@ impl Set { ensures #[trigger] self.union(t).filter_map(f) == self.filter_map(f).union(t.filter_map(f)), { - broadcast use group_set_axioms; + broadcast use group_set_lemmas; + broadcast use Set::lemma_flatten_contains; + broadcast use Set::lemma_map_contains; let lhs = self.union(t).filter_map(f); let rhs = self.filter_map(f).union(t.filter_map(f)); @@ -653,67 +622,9 @@ impl Set { assert(lhs =~= rhs); } - /// `map` preserves finiteness - pub proof fn lemma_map_finite(self, f: spec_fn(A) -> B) - requires - self.finite(), - ensures - self.map(f).finite(), - decreases self.len(), - { - broadcast use group_set_axioms; - broadcast use lemma_set_empty_equivalency_len; - - if self.len() == 0 { - assert(forall|elem: A| !(#[trigger] self.contains(elem))); - assert forall|res: B| #[trigger] self.map(f).contains(res) implies false by { - let x = choose|x: A| self.contains(x) && f(x) == res; - } - assert(self.map(f) =~= Set::::empty()); - } else { - let x = choose|x: A| self.contains(x); - assert(self.map(f).contains(f(x))); - self.remove(x).lemma_map_finite(f); - assert(self.remove(x).insert(x) == self); - assert(self.map(f) == self.remove(x).map(f).insert(f(x))); - } - } - - pub broadcast proof fn lemma_map_by_finite(self, fwd: spec_fn(A) -> B, rev: spec_fn(B) -> A) - requires - self.finite(), - ensures - #[trigger] self.map_by(fwd, rev).finite(), - { - broadcast use lemma_set_subset_finite; - - assert(self.map_by(fwd, rev).subset_of(self.map(fwd))); - self.lemma_map_finite(fwd); - } - - pub broadcast proof fn lemma_map_flatten_by_finite( - self, - fwd: spec_fn(A) -> Set, - rev: spec_fn(B) -> A, - ) - requires - self.finite(), - forall|a: A| self.contains(a) ==> fwd(a).finite(), - ensures - #[trigger] self.map_flatten_by(fwd, rev).finite(), - { - broadcast use lemma_set_subset_finite; - - let s1 = self.map_flatten_by(fwd, rev); - let s2 = self.map(fwd).flatten(); - assert forall|b: B| s1.contains(b) implies s2.contains(b) by { - assert(self.map(fwd).contains(fwd(rev(b)))); - } - assert(s1.subset_of(s2)); - self.lemma_map_finite(fwd); - self.map(fwd).lemma_flatten_finite(); - } - + /// If `self` is a subset of `s2`, and all elements of `s2` + /// satisfy predicate `p`, then all elements of `self` satisfy + /// predicate `p`. pub broadcast proof fn lemma_set_all_subset(self, s2: Set, p: spec_fn(A) -> bool) requires #[trigger] self.subset_of(s2), @@ -721,41 +632,18 @@ impl Set { ensures #[trigger] self.all(p), { - broadcast use group_set_axioms; - - } - - /// `filter_map` preserves finiteness. - pub broadcast proof fn lemma_filter_map_finite(self, f: spec_fn(A) -> Option) - requires - self.finite(), - ensures - #[trigger] self.filter_map(f).finite(), - decreases self.len(), - { - broadcast use group_set_axioms; - broadcast use Set::lemma_filter_map_insert; + broadcast use group_set_lemmas; - let mapped = self.filter_map(f); - if self.len() == 0 { - assert(self.filter_map(f) =~= Set::::empty()); - } else { - let elem = self.choose(); - self.remove(elem).lemma_filter_map_finite(f); - assert(self =~= self.remove(elem).insert(elem)); - } } /// Conversion to a sequence and back to a set is the identity function. pub broadcast proof fn lemma_to_seq_to_set_id(self) - requires - self.finite(), ensures #[trigger] self.to_seq().to_set() =~= self, decreases self.len(), { - broadcast use group_set_axioms; broadcast use lemma_set_empty_equivalency_len; + broadcast use Seq::to_set_ensures; broadcast use super::seq_lib::group_seq_properties; if self.len() == 0 { @@ -770,69 +658,101 @@ impl Set { } impl Set> { - pub open spec fn flatten(self) -> Set { + /// This function creates a set from all the elements of all the elements + /// of `self`. + pub closed spec fn flatten(self) -> Set { Set::new( |elem| exists|elem_s: Set| #[trigger] self.contains(elem_s) && elem_s.contains(elem), - ) + ).unwrap() + } + + /// This helper lemma demonstrates that `Self::flatten` is finite, + /// so it produces a valid `Set`. + proof fn lemma_flatten_finite(self) + ensures + ISet::new( + |elem| + exists|elem_s: Set| #[trigger] + self.contains(elem_s) && elem_s.contains(elem), + ).finite(), + decreases self.len(), + { + let flatten_f = |elem| + exists|elem_s: Set| #[trigger] self.contains(elem_s) && elem_s.contains(elem); + if forall|s: Set| !self.contains(s) { + assert(self =~= Set::>::empty()); + assert(ISet::new(flatten_f) =~= ISet::::empty()); + } else { + let s = choose|s: Set| self.contains(s); + self.remove(s).lemma_flatten_finite(); + let flatten_remove_f = |elem| + exists|elem_s: Set| #[trigger] + self.remove(s).contains(elem_s) && elem_s.contains(elem); + assert(s.to_iset().finite()); + assert(ISet::new(flatten_f) =~= ISet::new(flatten_remove_f).union(s.to_iset())); + } } + /// Since `Self::flatten` is `closed`, this broadcast lemma is + /// needed to make its semantics visible to the verifier. + pub broadcast proof fn lemma_flatten_contains(self, elem: A) + ensures + #[trigger] self.flatten().contains(elem) <==> (exists|elem_s: Set| #[trigger] + self.contains(elem_s) && elem_s.contains(elem)), + { + self.lemma_flatten_finite(); + } + + /// Flattening then unioning with another set is equivalent to + /// inserting that other set and then flattening. pub broadcast proof fn flatten_insert_union_commute(self, other: Set) ensures self.flatten().union(other) =~= #[trigger] self.insert(other).flatten(), { - broadcast use group_set_axioms; + broadcast use Set::lemma_flatten_contains; + broadcast use Set::lemma_map_contains; let lhs = self.flatten().union(other); let rhs = self.insert(other).flatten(); assert forall|elem: A| lhs.contains(elem) implies rhs.contains(elem) by { - if exists|s: Set| self.contains(s) && s.contains(elem) { - let s = choose|s: Set| self.contains(s) && s.contains(elem); + if self.flatten().contains(elem) { + self.lemma_flatten_contains(elem); + let s = choose|s: Set| #[trigger] self.contains(s) && s.contains(elem); assert(self.insert(other).contains(s)); assert(s.contains(elem)); } else { + assert(other.contains(elem)); assert(self.insert(other).contains(other)); } - } - } - - pub proof fn lemma_flatten_finite(self) - requires - self.finite(), - forall|s: Set| self.contains(s) ==> #[trigger] s.finite(), - ensures - self.flatten().finite(), - decreases self.len(), - { - broadcast use group_set_axioms; - - if self.len() == 0 { - assert(self.flatten() =~= Set::::empty()); - } else { - let s = self.choose(); - let self2 = self.remove(s); - self2.lemma_flatten_finite(); - self2.flatten_insert_union_commute(s); + self.insert(other).lemma_flatten_contains(elem); } } } pub trait FiniteRange: Sized { + spec fn in_range(i: Self, lo: Self, hi: Self) -> bool; + spec fn range_set(lo: Self, hi: Self) -> Set; spec fn range_len(lo: Self, hi: Self) -> nat; proof fn range_properties(lo: Self, hi: Self) ensures - Self::range_set(lo, hi).finite(), + forall|i: Self| #[trigger] + Self::range_set(lo, hi).contains(i) <==> Self::in_range(i, lo, hi), Self::range_set(lo, hi).len() == Self::range_len(lo, hi), ; } +/// This public broadcast lemma shows that when `A` has trait +/// `FiniteRange`, `A::range_set(lo, hi)` has the expected properties. +/// That is, it contains all values `a: A` such that `lo <= a < hi`, +/// and its length is `hi - lo`. pub broadcast proof fn range_set_properties(lo: A, hi: A) ensures - (#[trigger] A::range_set(lo, hi)).finite(), - A::range_set(lo, hi).len() == A::range_len(lo, hi), + forall|i: A| #[trigger] A::range_set(lo, hi).contains(i) <==> A::in_range(i, lo, hi), + (#[trigger] A::range_set(lo, hi)).len() == A::range_len(lo, hi), { A::range_properties(lo, hi); } @@ -840,14 +760,17 @@ pub broadcast proof fn range_set_properties(lo: A, hi: A) pub trait FiniteFull: Sized { proof fn full_properties() ensures - Set::::full().finite(), + Set::::full() is Some, ; } +/// This public broadcast lemma shows that when `A` has trait +/// `FiniteRange`, `A::full()` has the expected propery of containing +/// all values of type `A`. pub broadcast proof fn full_set_properties() ensures #![trigger Set::::full()] - (Set::::full()).finite(), + Set::::full() is Some, { A::full_properties(); } @@ -867,7 +790,7 @@ impl Set { impl Set { #[verifier::inline] pub open spec fn from_finite_type(f: spec_fn(A) -> bool) -> Set { - Set::::full().filter(f) + Set::::full().unwrap().filter(f) } } @@ -878,8 +801,11 @@ macro_rules! range_impls { $( verus! { impl FiniteRange for $t { + open spec fn in_range(i: Self, lo: Self, hi: Self) -> bool { + lo <= i < hi + } open spec fn range_set(lo: Self, hi: Self) -> Set { - Set::new(|i: Self| lo <= i < hi) + Set::new(|i: Self| Self::in_range(i, lo, hi)).unwrap() } open spec fn range_len(lo: Self, hi: Self) -> nat { if lo <= hi { (hi - lo) as nat } else { 0 } @@ -887,17 +813,33 @@ macro_rules! range_impls { proof fn range_properties(lo: Self, hi: Self) decreases hi - lo { - broadcast use axiom_set_empty_finite; - broadcast use axiom_set_empty_len; - broadcast use axiom_set_insert_finite; + proof fn range_properties_helper(lo: $t, hi: $t) + ensures + ISet::<$t>::new(|i: $t| $t::in_range(i, lo, hi)).finite(), + decreases + hi - lo, + { + if lo >= hi { + assert(ISet::<$t>::new(|i: $t| $t::in_range(i, lo, hi)) =~= ISet::<$t>::empty()); + } + else { + let hi_minus_1: $t = (hi - 1) as $t; + assert(hi_minus_1 == hi - 1); + range_properties_helper(lo, hi_minus_1); + assert(ISet::<$t>::new(|i: $t| $t::in_range(i, lo, hi)) =~= + ISet::<$t>::new(|i: $t| $t::in_range(i, lo, hi_minus_1)).insert(hi_minus_1)); + } + } + range_properties_helper(lo, hi); if hi <= lo { - assert(Self::range_set(lo, hi).is_empty()); + assert(Self::range_set(lo, hi) =~= Set::::empty()); } else { let hi1 = (hi - 1) as $t; Self::range_properties(lo, hi1); + assert(ISet::new(|i: Self| Self::in_range(i, lo, hi)) =~= + ISet::new(|i: Self| Self::in_range(i, lo, hi1)).insert(hi1)); assert(Self::range_set(lo, hi) == Self::range_set(lo, hi1).insert(hi1)); - axiom_set_insert_finite(Self::range_set(lo, hi1), hi1); } } } @@ -912,10 +854,32 @@ macro_rules! full_impls { verus! { impl FiniteFull for $t { proof fn full_properties() { - broadcast use axiom_set_insert_finite; + proof fn full_properties_helper(lo: $t, hi: $t) + requires + lo <= hi, + ensures + ISet::<$t>::new(|a: $t| lo <= a && a <= hi).finite(), + decreases + hi - lo, + { + if lo == hi { + assert(ISet::<$t>::new(|a: $t| lo <= a && a <= hi) =~= iset![lo]); + } + else { + let hi_minus_1: $t = (hi - 1) as $t; + assert(hi_minus_1 == hi - 1); + full_properties_helper(lo, hi_minus_1); + assert(ISet::<$t>::new(|a: $t| lo <= a && a <= hi) =~= + ISet::<$t>::new(|a: $t| lo <= a && a <= hi_minus_1).insert(hi)); + } + } - assert(Set::<$t>::full() == Set::range_inclusive($t::MIN, $t::MAX)); - <$t as FiniteRange>::range_properties($t::MIN, $t::MAX); + full_properties_helper($t::MIN, $t::MAX); + assert(ISet::<$t>::new(|a: $t| true) =~= + ISet::<$t>::new(|a: $t| $t::MIN <= a && a <= $t::MAX)); + assert(Set::<$t>::full() is Some); + Self::range_properties($t::MIN, $t::MAX); + assert(Set::<$t>::full().unwrap() == Set::range_inclusive($t::MIN, $t::MAX)); } } } // verus! @@ -943,7 +907,8 @@ pub proof fn lemma_sets_eq_iff_injective_map_eq(s1: Set, s2: Set, f: ensures (s1 == s2) <==> (s1.map(f) == s2.map(f)), { - broadcast use group_set_axioms; + broadcast use group_set_lemmas; + broadcast use Set::lemma_map_contains; if (s1.map(f) == s2.map(f)) { assert(s1.map(f).len() == s2.map(f).len()); @@ -961,11 +926,12 @@ pub proof fn lemma_sets_eq_iff_injective_map_eq(s1: Set, s2: Set, f: /// Two sets are equal iff applying an injective (in the union of the sets) function `f` to each set produces equal sets. pub proof fn lemma_sets_eq_iff_injective_map_on_eq(s1: Set, s2: Set, f: spec_fn(T) -> S) requires - super::relations::injective_on(f, s1 + s2), + (s1 + s2).injective_on(f), ensures (s1 == s2) <==> (s1.map(f) == s2.map(f)), { - broadcast use group_set_axioms; + broadcast use group_set_lemmas; + broadcast use Set::lemma_map_contains; if (s1.map(f) == s2.map(f)) { assert(s1.map(f).len() == s2.map(f).len()); @@ -980,79 +946,9 @@ pub proof fn lemma_sets_eq_iff_injective_map_on_eq(s1: Set, s2: Set, } } -/// The result of inserting an element `a` into a set `s` is finite iff `s` is finite. -pub broadcast proof fn lemma_set_insert_finite_iff(s: Set, a: A) - ensures - #[trigger] s.insert(a).finite() <==> s.finite(), -{ - if s.insert(a).finite() { - if s.contains(a) { - assert(s == s.insert(a)); - } else { - assert(s == s.insert(a).remove(a)); - } - } - assert(s.insert(a).finite() ==> s.finite()); -} - -/// The result of removing an element `a` into a set `s` is finite iff `s` is finite. -pub broadcast proof fn lemma_set_remove_finite_iff(s: Set, a: A) - ensures - #[trigger] s.remove(a).finite() <==> s.finite(), -{ - if s.remove(a).finite() { - if s.contains(a) { - assert(s == s.remove(a).insert(a)); - } else { - assert(s == s.remove(a)); - } - } -} - -/// The union of two sets is finite iff both sets are finite. -pub broadcast proof fn lemma_set_union_finite_iff(s1: Set, s2: Set) - ensures - #[trigger] s1.union(s2).finite() <==> s1.finite() && s2.finite(), -{ - if s1.union(s2).finite() { - lemma_set_union_finite_implies_sets_finite(s1, s2); - } -} - -pub proof fn lemma_set_union_finite_implies_sets_finite(s1: Set, s2: Set) - requires - s1.union(s2).finite(), - ensures - s1.finite(), - s2.finite(), - decreases s1.union(s2).len(), -{ - if s1.union(s2) =~= set![] { - assert(s1 =~= set![]); - assert(s2 =~= set![]); - } else { - let a = s1.union(s2).choose(); - assert(s1.remove(a).union(s2.remove(a)) == s1.union(s2).remove(a)); - axiom_set_remove_len(s1.union(s2), a); - lemma_set_union_finite_implies_sets_finite(s1.remove(a), s2.remove(a)); - assert(forall|s: Set| - #![auto] - s.remove(a).insert(a) == if s.contains(a) { - s - } else { - s.insert(a) - }); - lemma_set_insert_finite_iff(s1, a); - lemma_set_insert_finite_iff(s2, a); - } -} - /// The size of a union of two sets is less than or equal to the size of /// both individual sets combined. pub proof fn lemma_len_union(s1: Set, s2: Set) - requires - s1.finite(), - s2.finite(), ensures s1.union(s2).len() <= s1.len() + s2.len(), decreases s1.len(), @@ -1073,9 +969,6 @@ pub proof fn lemma_len_union(s1: Set, s2: Set) /// The size of a union of two sets is greater than or equal to the size of /// both individual sets. pub proof fn lemma_len_union_ind(s1: Set, s2: Set) - requires - s1.finite(), - s2.finite(), ensures s1.union(s2).len() >= s1.len(), s1.union(s2).len() >= s2.len(), @@ -1098,8 +991,6 @@ pub proof fn lemma_len_union_ind(s1: Set, s2: Set) /// The size of the intersection of finite set `s1` and set `s2` is less than or equal to the size of `s1`. pub proof fn lemma_len_intersect(s1: Set, s2: Set) - requires - s1.finite(), ensures s1.intersect(s2).len() <= s1.len(), decreases s1.len(), @@ -1117,33 +1008,16 @@ pub proof fn lemma_len_intersect(s1: Set, s2: Set) /// the size of `s2` and `s1` must be finite. pub proof fn lemma_len_subset(s1: Set, s2: Set) requires - s2.finite(), s1.subset_of(s2), ensures s1.len() <= s2.len(), - s1.finite(), { lemma_len_intersect::(s2, s1); assert(s2.intersect(s1) =~= s1); } -/// A subset of a finite set `s` is finite. -pub broadcast proof fn lemma_set_subset_finite(s: Set, sub: Set) - requires - s.finite(), - sub.subset_of(s), - ensures - #![trigger sub.subset_of(s)] - sub.finite(), -{ - let complement = s.difference(sub); - assert(sub =~= s.difference(complement)); -} - /// The size of the difference of finite set `s1` and set `s2` is less than or equal to the size of `s1`. pub proof fn lemma_len_difference(s1: Set, s2: Set) - requires - s1.finite(), ensures s1.difference(s2).len() <= s1.len(), decreases s1.len(), @@ -1159,7 +1033,7 @@ pub proof fn lemma_len_difference(s1: Set, s2: Set) /// Creates a finite set of integers in the range [lo, hi). pub open spec fn set_int_range(lo: int, hi: int) -> Set { - Set::new(|i: int| lo <= i && i < hi) + Set::::range(lo, hi) } /// If a set solely contains integers in the range [a, b), then its size is @@ -1168,24 +1042,18 @@ pub proof fn lemma_int_range(lo: int, hi: int) requires lo <= hi, ensures - set_int_range(lo, hi).finite(), + forall|j: int| set_int_range(lo, hi).contains(j) <==> lo <= j < hi, set_int_range(lo, hi).len() == hi - lo, decreases hi - lo, { - if lo == hi { - assert(set_int_range(lo, hi) =~= Set::empty()); - } else { - lemma_int_range(lo, hi - 1); - assert(set_int_range(lo, hi - 1).insert(hi - 1) =~= set_int_range(lo, hi)); - } + broadcast use range_set_properties; + } /// If x is a subset of y and the size of x is equal to the size of y, x is equal to y. pub proof fn lemma_subset_equality(x: Set, y: Set) requires x.subset_of(y), - x.finite(), - y.finite(), x.len() == y.len(), ensures x =~= y, @@ -1204,15 +1072,14 @@ pub proof fn lemma_subset_equality(x: Set, y: Set) /// another set, the two sets have the same size. pub proof fn lemma_map_size(x: Set, y: Set, f: spec_fn(A) -> B) requires - x.finite(), - injective_on(f, x), + x.injective_on(f), x.map(f) == y, ensures - y.finite(), x.len() == y.len(), decreases x.len(), { broadcast use group_set_properties; + broadcast use Set::lemma_map_contains; if x.len() == 0 { if !y.is_empty() { @@ -1230,14 +1097,13 @@ pub proof fn lemma_map_size(x: Set, y: Set, f: spec_fn(A) -> B) /// another set, the constructed set's length is at most the original's pub proof fn lemma_map_size_bound(x: Set, y: Set, f: spec_fn(A) -> B) requires - x.finite(), x.map(f) == y, ensures - y.finite(), y.len() <= x.len(), decreases x.len(), { broadcast use group_set_properties; + broadcast use Set::lemma_map_contains; if x.is_empty() { if !y.is_empty() { @@ -1327,8 +1193,6 @@ pub broadcast proof fn lemma_set_disjoint(a: Set, b: Set) /// Set `s` has length 0 if and only if it is equal to the empty set. If `s` has length greater than 0, /// Then there must exist an element `x` such that `s` contains `x`. pub broadcast proof fn lemma_set_empty_equivalency_len(s: Set) - requires - s.finite(), ensures #![trigger s.len()] (s.len() == 0 <==> s == Set::::empty()) && (s.len() != 0 ==> exists|x: A| s.contains(x)), @@ -1342,7 +1206,7 @@ pub broadcast proof fn lemma_set_empty_equivalency_len(s: Set) if exists|a: A| s.contains(a) { let a = s.choose(); assert(s.remove(a).len() == s.len() - 1) by { - axiom_set_remove_len(s, a); + lemma_set_remove_len(s, a); } } } @@ -1354,9 +1218,6 @@ pub broadcast proof fn lemma_set_empty_equivalency_len(s: Set) /// If sets `a` and `b` are disjoint, meaning they share no elements in common, then the length /// of the union `a + b` is equal to the sum of the lengths of `a` and `b`. pub broadcast proof fn lemma_set_disjoint_lens(a: Set, b: Set) - requires - a.finite(), - b.finite(), ensures a.disjoint(b) ==> #[trigger] (a + b).len() == a.len() + b.len(), decreases a.len(), @@ -1374,7 +1235,7 @@ pub broadcast proof fn lemma_set_disjoint_lens(a: Set, b: Set) } /// Two sets are disjoint iff their intersection is empty -pub proof fn lemma_disjoint_iff_empty_intersection(a: Set, b: Set) +pub proof fn lemma_set_disjoint_iff_empty_intersection(a: Set, b: Set) ensures a.disjoint(b) <==> a.intersect(b).is_empty(), { @@ -1401,9 +1262,6 @@ pub proof fn lemma_disjoint_iff_empty_intersection(a: Set, b: Set) /// The length of the union between two sets added to the length of the intersection between the /// two sets is equal to the sum of the lengths of the two sets. pub broadcast proof fn lemma_set_intersect_union_lens(a: Set, b: Set) - requires - a.finite(), - b.finite(), ensures #[trigger] (a + b).len() + #[trigger] a.intersect(b).len() == a.len() + b.len(), decreases a.len(), @@ -1433,9 +1291,6 @@ pub broadcast proof fn lemma_set_intersect_union_lens(a: Set, b: Set) /// The length of the set difference `A \ B` is equal to the length of `A` minus the length of the /// intersection `A ∩ B`. pub broadcast proof fn lemma_set_difference_len(a: Set, b: Set) - requires - a.finite(), - b.finite(), ensures (#[trigger] a.difference(b).len() + b.difference(a).len() + a.intersect(b).len() == (a + b).len()) && (a.difference(b).len() == a.len() - a.intersect(b).len()), @@ -1479,18 +1334,18 @@ pub broadcast group group_set_properties { lemma_set_empty_equivalency_len, } -pub broadcast proof fn axiom_is_empty(s: Set) +pub broadcast proof fn lemma_set_is_empty(s: Set) requires !(#[trigger] s.is_empty()), ensures exists|a: A| s.contains(a), { - admit(); // REVIEW, should this be in `set`, or have a proof? + super::iset_lib::axiom_iset_is_empty(s.to_iset()); } -pub broadcast proof fn axiom_is_empty_len0(s: Set) +pub broadcast proof fn lemma_set_is_empty_len0(s: Set) ensures - #[trigger] s.is_empty() <==> (s.finite() && s.len() == 0), + #[trigger] s.is_empty() <==> s.len() == 0, { } @@ -1562,11 +1417,11 @@ macro_rules! assert_sets_equal_internal { } pub broadcast group group_set_lib_default { - axiom_is_empty, - axiom_is_empty_len0, - lemma_set_subset_finite, - Set::lemma_map_by_finite, - Set::lemma_map_flatten_by_finite, + lemma_set_is_empty, + lemma_set_is_empty_len0, + Set::lemma_map_contains, + Set::lemma_map_by_contains, + Set::lemma_map_flatten_by_contains, range_set_properties, full_set_properties, } diff --git a/source/vstd/simple_pptr.rs b/source/vstd/simple_pptr.rs index cc54b58dea..5b1bd1d738 100644 --- a/source/vstd/simple_pptr.rs +++ b/source/vstd/simple_pptr.rs @@ -169,7 +169,7 @@ pub tracked struct PointsTo { broadcast use { super::raw_ptr::group_raw_ptr_axioms, super::set_lib::group_set_lib_default, - super::set::group_set_axioms}; + super::set::group_set_lemmas}; impl PPtr { /// Use `addr()` instead @@ -372,6 +372,17 @@ impl PPtr { p != 0, ; let tracked emp = PointsToRaw::empty(Provenance::null()); + proof { + assert forall|i: int| #[trigger] + Set::::range(p as int, p as int).contains(i) == Set::< + int, + >::empty().contains(i) by {} + super::set::axiom_set_ext_equal( + Set::::range(p as int, p as int), + Set::::empty(), + ); + assert(emp.is_range(p as int, 0)); + } let tracked points_to = emp.into_typed(p); let tracked pt = PointsTo { points_to, exposed: IsExposed::null(), dealloc: None }; let pptr = PPtr(p, PhantomData); diff --git a/source/vstd/state_machine_internal.rs b/source/vstd/state_machine_internal.rs index 6b1e5963f6..4a8d763d03 100644 --- a/source/vstd/state_machine_internal.rs +++ b/source/vstd/state_machine_internal.rs @@ -2,10 +2,13 @@ #![allow(unused_imports)] #![doc(hidden)] +use super::imap::*; +use super::iset::*; use super::map::*; use super::pervasive::*; use super::prelude::*; use super::seq::*; +use super::set::*; #[cfg_attr(verus_keep_ghost, verifier::external_body)] /* vattr */ #[cfg_attr(verus_keep_ghost, verifier::accept_recursive_types(T))] @@ -76,6 +79,18 @@ pub fn assert_add_set(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_add_iset(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: to add a singleton iset, the value must not be in the iset before the update" + )] /* vattr */ + b, + ); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_add_bool(b: bool) { @@ -100,6 +115,18 @@ pub fn assert_add_map(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_add_imap(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the given key must be absent from the imap before the update" + )] /* vattr */ + b, + ); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_add_persistent_map(b: bool) { @@ -114,10 +141,10 @@ pub fn assert_add_persistent_map(b: bool) { #[cfg(verus_keep_ghost)] #[verifier::proof] -pub fn assert_add_persistent_option(b: bool) { +pub fn assert_add_persistent_imap(b: bool) { requires( #[verifier::custom_err( - "unable to prove inherent safety condition: if the previous value is Some(_), then this existing value must agree with the newly provided value" + "unable to prove inherent safety condition: if the key is already in the imap, its existing value must agree with the provided value" )] /* vattr */ b, ); @@ -126,16 +153,23 @@ pub fn assert_add_persistent_option(b: bool) { #[cfg(verus_keep_ghost)] #[verifier::proof] -pub fn assert_withdraw_option(b: bool) { +pub fn assert_add_persistent_option(b: bool) { requires( #[verifier::custom_err( - "unable to prove inherent safety condition: the given value to be withdrawn must be stored before the withdraw" + "unable to prove inherent safety condition: if the previous value is Some(_), then this existing value must agree with the newly provided value" )] /* vattr */ b, ); ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_withdraw_option(b: bool) { + requires(b); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_deposit_option(b: bool) { @@ -172,6 +206,18 @@ pub fn assert_withdraw_map(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_withdraw_imap(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the value to be withdrawn must be stored at the given key before the withdraw" + )] /* vattr */ + b, + ); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_deposit_map(b: bool) { @@ -184,6 +230,18 @@ pub fn assert_deposit_map(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_deposit_imap(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the given key must be absent from the imap before the deposit" + )] /* vattr */ + b, + ); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_guard_map(b: bool) { @@ -196,6 +254,18 @@ pub fn assert_guard_map(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_guard_imap(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the value being guarded must be stored at the given key" + )] /* vattr */ + b, + ); + ensures(b); +} + // SpecialOps (with general element) #[cfg(verus_keep_ghost)] @@ -222,6 +292,18 @@ pub fn assert_general_add_set(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_general_add_iset(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the isets being composed must be disjoint" + )] /* vattr */ + b, + ); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_general_add_bool(b: bool) { @@ -246,6 +328,18 @@ pub fn assert_general_add_map(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_general_add_imap(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the key domains of the imaps being composed must be disjoint" + )] /* vattr */ + b, + ); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_general_add_persistent_map(b: bool) { @@ -258,6 +352,18 @@ pub fn assert_general_add_persistent_map(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_general_add_persistent_imap(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the imaps being composed must agree on their values for any key in both domains" + )] /* vattr */ + b, + ); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_general_add_persistent_option(b: bool) { @@ -318,6 +424,18 @@ pub fn assert_general_withdraw_map(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_general_withdraw_imap(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the imap being withdrawn must be a submap of the stored imap" + )] /* vattr */ + b, + ); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_general_deposit_map(b: bool) { @@ -330,6 +448,18 @@ pub fn assert_general_deposit_map(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_general_deposit_imap(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the key domains of the imaps being composed must be disjoint" + )] /* vattr */ + b, + ); + ensures(b); +} + #[cfg(verus_keep_ghost)] #[verifier::proof] pub fn assert_general_guard_map(b: bool) { @@ -342,6 +472,18 @@ pub fn assert_general_guard_map(b: bool) { ensures(b); } +#[cfg(verus_keep_ghost)] +#[verifier::proof] +pub fn assert_general_guard_imap(b: bool) { + requires( + #[verifier::custom_err( + "unable to prove inherent safety condition: the imap being guarded must be a submap of the stored imap" + )] /* vattr */ + b, + ); + ensures(b); +} + // used by the `update field[idx] = ...;` syntax // // note: the built-in rust trait IndexMut doesn't work here @@ -372,7 +514,7 @@ impl Seq { } #[doc(hidden)] -impl Map { +impl IMap { // note that despite the name, this is allowed to insert #[verifier::inline] pub open spec fn update_at_index(self, k: K, v: V) -> Self { diff --git a/source/vstd/std_specs/btree.rs b/source/vstd/std_specs/btree.rs index 2c7067c8fd..ebc7118108 100644 --- a/source/vstd/std_specs/btree.rs +++ b/source/vstd/std_specs/btree.rs @@ -271,8 +271,7 @@ pub open spec fn btree_map_deep_view_impl, ) -> Map { Map::new( - |k: Key::V| - exists|orig_k: Key| #[trigger] m@.contains_key(orig_k) && k == orig_k.deep_view(), + m@.dom().map(|k: Key| k.deep_view()), |dk: Key::V| { let k = choose|k: Key| m@.contains_key(k) && #[trigger] k.deep_view() == dk; @@ -362,12 +361,6 @@ pub broadcast axiom fn axiom_btree_map_deepview_borrow< #[trigger] contains_borrowed_key(m@, k) <==> m.deep_view().contains_key(k@), ; -/// A `Map` constructed from a `BTreeMap` is always finite. -pub broadcast axiom fn axiom_btree_map_view_finite_dom(m: BTreeMap) - ensures - #[trigger] m@.dom().finite(), -; - pub uninterp spec fn spec_btree_map_len( m: &BTreeMap, ) -> usize; @@ -964,7 +957,6 @@ pub broadcast group group_btree_axioms { axiom_maps_deref_key_to_value, axiom_maps_box_key_to_value, axiom_btree_map_deepview_borrow, - axiom_btree_map_view_finite_dom, axiom_spec_btree_map_len, axiom_set_box_key_removed, axiom_set_contains_deref_key, diff --git a/source/vstd/std_specs/hash.rs b/source/vstd/std_specs/hash.rs index c133778e02..63844d211c 100644 --- a/source/vstd/std_specs/hash.rs +++ b/source/vstd/std_specs/hash.rs @@ -477,8 +477,7 @@ pub open spec fn hash_map_deep_view_impl< A: core::alloc::Allocator, >(m: HashMap) -> Map { Map::new( - |k: Key::V| - exists|orig_k: Key| #[trigger] m@.contains_key(orig_k) && k == orig_k.deep_view(), + m@.dom().map(|k: Key| k.deep_view()), |dk: Key::V| { let k = choose|k: Key| m@.contains_key(k) && #[trigger] k.deep_view() == dk; @@ -516,16 +515,29 @@ pub broadcast proof fn lemma_hashmap_deepview_properties(m: HashMap) - ensures - #[trigger] m@.dom().finite(), -{ - admit(); -} - pub uninterp spec fn spec_hash_map_len( m: &HashMap, ) -> usize; @@ -1508,7 +1512,6 @@ pub broadcast group group_hash_axioms { axiom_maps_deref_key_to_value, axiom_maps_box_key_to_value, axiom_hashmap_deepview_borrow, - axiom_hashmap_view_finite_dom, axiom_bool_obeys_hash_table_key_model, axiom_u8_obeys_hash_table_key_model, axiom_u16_obeys_hash_table_key_model, diff --git a/source/vstd/tokens.rs b/source/vstd/tokens.rs index 541f77f411..acae208b72 100644 --- a/source/vstd/tokens.rs +++ b/source/vstd/tokens.rs @@ -12,9 +12,13 @@ verus_! { #[verusfmt::skip] broadcast use { + super::iset::group_iset_lemmas, + super::iset_lib::group_iset_lib_default, + super::imap::group_imap_lemmas, + super::map::group_map_lemmas, + super::set::group_set_lemmas, super::set_lib::group_set_lib_default, - super::set::group_set_axioms, - super::map::group_map_axioms }; +}; /// Unique identifier for every VerusSync instance. /// Every "Token" and "Instance" object has an `InstanceId`. These ID values must agree @@ -65,10 +69,12 @@ pub trait UniqueValueToken : ValueToken { /// Interface for VerusSync tokens created for a field marked with the /// `map` or `persistent_map` strategies. /// -/// | VerusSync Strategy | Field type | Token trait | -/// |---------------------|-------------|------------------------| -/// | `map` | `Map` | [`UniqueKeyValueToken`](`UniqueKeyValueToken`) | -/// | `persistent_map` | `Map` | `KeyValueToken + Copy` | +/// | VerusSync Strategy | Field type | Token trait | +/// |---------------------|--------------|------------------------| +/// | `map` | `Map ` | [`UniqueKeyValueToken`](`UniqueKeyValueToken`) | +/// | `imap` | `IMap` | [`UniqueKeyValueToken`](`UniqueKeyValueToken`) | +/// | `persistent_map` | `Map` | `KeyValueToken + Copy` | +/// | `persistent_imap` | `IMap` | `KeyValueToken + Copy` | /// /// For the cases where the tokens are not `Copy`, then token is necessarily _unique_ /// per-instance, per-key; the sub-trait, [`UniqueKeyValueToken`](`UniqueKeyValueToken`), provides @@ -172,10 +178,12 @@ pub trait MonotonicCountToken : Sized { /// Interface for VerusSync tokens created for a field marked with the /// `set`, `persistent_set` or `multiset` strategies. /// -/// | VerusSync Strategy | Field type | Token trait | -/// |---------------------|-------------|------------------------| -/// | `set` | `Set` | [`UniqueElementToken`](`UniqueElementToken`) | -/// | `persistent_set` | `Set` | `ElementToken + Copy` | +/// | VerusSync Strategy | Field type | Token trait | +/// |---------------------|--------------|------------------------| +/// | `set` | `Set` | [`UniqueElementToken`](`UniqueElementToken`) | +/// | `iset` | `ISet` | [`UniqueElementToken`](`UniqueElementToken`) | +/// | `persistent_set` | `Set` | `ElementToken + Copy` | +/// | `persistent_iset` | `ISet` | `ElementToken + Copy` | /// | `multiset` | `Multiset` | `ElementToken` | /// /// Each token represents a single element of the set or multiset. @@ -242,6 +250,121 @@ pub trait UniqueSimpleToken : SimpleToken { *final(self) == *old(self); } +#[verifier::reject_recursive_types(Key)] +pub tracked struct IMapToken + where Token: KeyValueToken +{ + ghost _v: PhantomData, + ghost inst: InstanceId, + tracked m: IMap, +} + +impl IMapToken + where Token: KeyValueToken +{ + #[verifier::type_invariant] + spec fn wf(self) -> bool { + forall |k| #[trigger] self.m.dom().contains(k) ==> self.m[k].key() == k + && self.m[k].instance_id() == self.inst + } + + pub closed spec fn instance_id(self) -> InstanceId { + self.inst + } + + pub closed spec fn map(self) -> IMap { + IMap::new( + |k: Key| self.m.dom().contains(k), + |k: Key| self.m[k].value(), + ) + } + + #[verifier::inline] + pub open spec fn dom(self) -> ISet { + self.map().dom() + } + + #[verifier::inline] + pub open spec fn spec_index(self, k: Key) -> Value { + self.map()[k] + } + + #[verifier::inline] + pub open spec fn index(self, k: Key) -> Value { + self.map()[k] + } + + pub proof fn empty(instance_id: InstanceId) -> (tracked s: Self) + ensures + s.instance_id() == instance_id, + s.map() == IMap::empty(), + { + let tracked s = Self { inst: instance_id, m: IMap::tracked_empty(), _v: PhantomData }; + assert(s.map() =~= IMap::empty()); + return s; + } + + pub proof fn insert(tracked &mut self, tracked token: Token) + requires + old(self).instance_id() == token.instance_id(), + ensures + final(self).instance_id() == old(self).instance_id(), + final(self).map() == old(self).map().insert(token.key(), token.value()), + { + use_type_invariant(&*self); + self.m.tracked_insert(token.key(), token); + assert(self.map() =~= old(self).map().insert(token.key(), token.value())); + } + + pub proof fn remove(tracked &mut self, key: Key) -> (tracked token: Token) + requires + old(self).map().dom().contains(key) + ensures + final(self).instance_id() == old(self).instance_id(), + final(self).map() == old(self).map().remove(key), + token.instance_id() == final(self).instance_id(), + token.key() == key, + token.value() == old(self).map()[key] + { + use_type_invariant(&*self); + let tracked t = self.m.tracked_remove(key); + assert(self.map() =~= old(self).map().remove(key)); + t + } + + pub proof fn into_map(tracked self) -> (tracked map: IMap) + ensures + map.dom() == self.map().dom(), + forall |key| + #![trigger(map.dom().contains(key))] + #![trigger(map.index(key))] + map.dom().contains(key) + ==> map[key].instance_id() == self.instance_id() + && map[key].key() == key + && map[key].value() == self.map()[key] + { + use_type_invariant(&self); + let tracked IMapToken { inst, m, _v } = self; + assert(m.dom() =~= self.map().dom()); + return m; + } + + pub proof fn from_map(instance_id: InstanceId, tracked map: IMap) -> (tracked s: Self) + requires + forall |key| #[trigger] map.dom().contains(key) ==> map[key].instance_id() == instance_id, + forall |key| #[trigger] map.dom().contains(key) ==> map[key].key() == key, + ensures + s.instance_id() == instance_id, + s.map().dom() == map.dom(), + forall |key| #[trigger] map.dom().contains(key) + ==> s.map()[key] == map[key].value() + { + let tracked s = IMapToken { inst: instance_id, m: map, _v: PhantomData }; + assert(map.dom() == s.map().dom()); + s + } +} + #[verifier::reject_recursive_types(Key)] pub tracked struct MapToken where Token: KeyValueToken @@ -266,7 +389,7 @@ impl MapToken pub closed spec fn map(self) -> Map { Map::new( - |k: Key| self.m.dom().contains(k), + self.m.dom(), |k: Key| self.m[k].value(), ) } @@ -357,6 +480,105 @@ impl MapToken } } +#[verifier::reject_recursive_types(Element)] +pub tracked struct ISetToken + where Token: ElementToken +{ + ghost inst: InstanceId, + tracked m: IMap, +} + +impl ISetToken + where Token: ElementToken +{ + #[verifier::type_invariant] + spec fn wf(self) -> bool { + forall |k| #[trigger] self.m.dom().contains(k) ==> self.m[k].element() == k + && self.m[k].instance_id() == self.inst + } + + pub closed spec fn instance_id(self) -> InstanceId { + self.inst + } + + pub closed spec fn set(self) -> ISet { + ISet::new( + |e: Element| self.m.dom().contains(e), + ) + } + + #[verifier::inline] + pub open spec fn contains(self, element: Element) -> bool { + self.set().contains(element) + } + + pub proof fn empty(instance_id: InstanceId) -> (tracked s: Self) + ensures + s.instance_id() == instance_id, + s.set() == ISet::empty(), + { + let tracked s = Self { inst: instance_id, m: IMap::tracked_empty() }; + assert(s.set() =~= ISet::empty()); + return s; + } + + pub proof fn insert(tracked &mut self, tracked token: Token) + requires + old(self).instance_id() == token.instance_id(), + ensures + final(self).instance_id() == old(self).instance_id(), + final(self).set() == old(self).set().insert(token.element()), + { + use_type_invariant(&*self); + self.m.tracked_insert(token.element(), token); + assert(self.set() =~= old(self).set().insert(token.element())); + } + + pub proof fn remove(tracked &mut self, element: Element) -> (tracked token: Token) + requires + old(self).set().contains(element) + ensures + final(self).instance_id() == old(self).instance_id(), + final(self).set() == old(self).set().remove(element), + token.instance_id() == final(self).instance_id(), + token.element() == element, + { + use_type_invariant(&*self); + let tracked t = self.m.tracked_remove(element); + assert(self.set() =~= old(self).set().remove(element)); + t + } + + pub proof fn into_map(tracked self) -> (tracked map: IMap) + ensures + map.dom() == self.set(), + forall |key| + #![trigger(map.dom().contains(key))] + #![trigger(map.index(key))] + map.dom().contains(key) + ==> map[key].instance_id() == self.instance_id() + && map[key].element() == key + { + use_type_invariant(&self); + let tracked ISetToken { inst, m } = self; + assert(m.dom() =~= self.set()); + return m; + } + + pub proof fn from_map(instance_id: InstanceId, tracked map: IMap) -> (tracked s: Self) + requires + forall |key| #[trigger] map.dom().contains(key) ==> map[key].instance_id() == instance_id, + forall |key| #[trigger] map.dom().contains(key) ==> map[key].element() == key, + ensures + s.instance_id() == instance_id, + s.set() == map.dom(), + { + let tracked s = ISetToken { inst: instance_id, m: map }; + assert(s.set() =~= map.dom()); + s + } +} + #[verifier::reject_recursive_types(Element)] pub tracked struct SetToken where Token: ElementToken @@ -379,9 +601,7 @@ impl SetToken } pub closed spec fn set(self) -> Set { - Set::new( - |e: Element| self.m.dom().contains(e), - ) + self.m.dom() } #[verifier::inline] @@ -461,14 +681,14 @@ pub tracked struct MultisetToken { ghost _v: PhantomData, ghost inst: InstanceId, - tracked m: Map, + tracked m: IMap, } -spec fn map_values(m: Map) -> Multiset { +spec fn map_values(m: IMap) -> Multiset { m.dom().fold(Multiset::empty(), |multiset: Multiset, k: K| multiset.insert(m[k])) } -proof fn map_values_insert_not_in(m: Map, k: K, v: V) +proof fn map_values_insert_not_in(m: IMap, k: K, v: V) requires !m.dom().contains(k) ensures @@ -477,7 +697,7 @@ proof fn map_values_insert_not_in(m: Map, k: K, v: V) assume(false); } -proof fn map_values_remove(m: Map, k: K) +proof fn map_values_remove(m: IMap, k: K) requires m.dom().contains(k) ensures @@ -487,21 +707,21 @@ proof fn map_values_remove(m: Map, k: K) } //proof fn map_values_empty_empty() -// ensures map_values(Map::::empty()) == Multiset::empty(), +// ensures map_values(IMap::::empty()) == Multiset::empty(), -spec fn fresh(m: Set) -> int { +spec fn fresh(m: ISet) -> int { m.find_unique_maximal(|a: int, b: int| a <= b) + 1 } -proof fn fresh_is_fresh(s: Set) +proof fn fresh_is_fresh(s: ISet) requires s.finite(), ensures !s.contains(fresh(s)) { assume(false); } -proof fn get_key_for_value(m: Map, value: V) -> (k: K) - requires map_values(m).contains(value), m.dom().finite(), +proof fn get_key_for_value(m: IMap, value: V) -> (k: K) + requires map_values(m).contains(value), ensures m.dom().contains(k), m[k] == value, { assume(false); @@ -522,7 +742,7 @@ impl MultisetToken self.inst } - spec fn map_elems(m: Map) -> Map { + spec fn map_elems(m: IMap) -> IMap { m.map_values(|t: Token| t.element()) } @@ -535,9 +755,9 @@ impl MultisetToken s.instance_id() == instance_id, s.multiset() == Multiset::empty(), { - let tracked s = Self { inst: instance_id, m: Map::tracked_empty(), _v: PhantomData, }; - broadcast use super::set::fold::lemma_fold_empty; - assert(Self::map_elems(Map::empty()) =~= Map::empty()); + let tracked s = Self { inst: instance_id, m: IMap::tracked_empty(), _v: PhantomData, }; + broadcast use super::iset::fold::lemma_fold_empty; + assert(Self::map_elems(IMap::empty()) =~= IMap::empty()); return s; } diff --git a/source/vstd/vstd.rs b/source/vstd/vstd.rs index 953c9e71f0..a483bc9267 100644 --- a/source/vstd/vstd.rs +++ b/source/vstd/vstd.rs @@ -42,7 +42,11 @@ pub mod future; pub mod hash_map; #[cfg(all(feature = "alloc", feature = "std"))] pub mod hash_set; +pub mod imap; +pub mod imap_lib; pub mod invariant; +pub mod iset; +pub mod iset_lib; #[cfg(verus_keep_ghost)] pub mod laws_cmp; #[cfg(verus_keep_ghost)] @@ -97,8 +101,10 @@ pub broadcast group group_vstd_default { // seq::group_seq_axioms, seq_lib::group_seq_lib_default, - map::group_map_axioms, - set::group_set_axioms, + map::group_map_lemmas, + set::group_set_lemmas, + imap::group_imap_lemmas, + iset::group_iset_lemmas, set_lib::group_set_lib_default, multiset::group_multiset_axioms, compute::all_spec_ensures, diff --git a/tools/veritas/run.sh b/tools/veritas/run.sh index 8e4f638165..1a85ced839 100644 --- a/tools/veritas/run.sh +++ b/tools/veritas/run.sh @@ -7,13 +7,39 @@ if [ "$(dirname "$0")" != "." ]; then exit 1 fi -docker run --platform=linux/amd64 \ +if command -v docker >/dev/null 2>&1; then + RUNTIME=docker +elif command -v podman >/dev/null 2>&1; then + RUNTIME=podman +else + echo "Neither docker nor podman found." + exit 1 +fi + +HOST_WORK_DIR="${HOST_WORK_DIR:-/tmp/veritas-work}" +mkdir -p "$HOST_WORK_DIR" output + +# Optional: mount a local Verus checkout for file:// verus_git_url in config. +# Example: +# LOCAL_VERUS_REPO=/home/user/work/verus \ +# bash run.sh run_configuration_local.toml +EXTRA_MOUNTS=() +if [ -n "${LOCAL_VERUS_REPO:-}" ]; then + if [ ! -d "$LOCAL_VERUS_REPO" ]; then + echo "LOCAL_VERUS_REPO is not a directory: $LOCAL_VERUS_REPO" + exit 1 + fi + EXTRA_MOUNTS+=("-v" "$LOCAL_VERUS_REPO:/root/local-verus") +fi + +"$RUNTIME" run --platform=linux/amd64 \ -v verus-veritas-repo-cache:/root/repos-cache \ -v $(pwd):/root/veritas \ - -v /root/work \ + -v "$HOST_WORK_DIR":/root/work \ -v verus-veritas-cargo-$RUST_VERSION-cache:/root/.cargo \ -v verus-veritas-z3-cache:/root/z3-cache \ -v verus-veritas-rustup-$RUST_VERSION:/root/.rustup \ -v $(pwd)/output:/root/output \ + "${EXTRA_MOUNTS[@]}" \ --rm \ veritas:rust-$RUST_VERSION $@ diff --git a/tools/veritas/run_configuration_all_local.toml b/tools/veritas/run_configuration_all_local.toml new file mode 100644 index 0000000000..61a5bdf986 --- /dev/null +++ b/tools/veritas/run_configuration_all_local.toml @@ -0,0 +1,40 @@ +verus_git_url = "file:///root/local-verus" +verus_revspec = "HEAD" +verus_features = ["singular"] +verus_verify_vstd = true + +[[project]] +name = "page-table" +git_url = "https://github.com/jonhnet/verified-nrkernel.git" +revspec = "jonh/sets-typed-finite" +crate_root = "page-table/src/lib.rs" +extra_args = ["--crate-type=lib", "--rlimit", "60", "--cfg", "feature=\"impl\""] + +[[project]] +name = "verified-storage" +git_url = "https://github.com/jonhnet/verified-storage.git" +revspec = "jonh/sets-typed-finite" +crate_root = "storage_node/src/lib.rs" +extra_args = [ + "-L", "dependency=deps_hack/target/debug/deps", + "--extern=deps_hack=deps_hack/target/debug/libdeps_hack.rlib", +] +prepare_script = """ +DEBIAN_FRONTEND=noninteractive apt-get update; apt-get install -y libpmem1 libpmemlog1 libpmem-dev libpmemlog-dev llvm-dev clang libclang-dev + +cd pmsafe +cargo clean +cargo build +cd .. + +cd deps_hack +cargo clean +cargo build +""" + +[[project]] +name = "node-replication" +git_url = "https://github.com/jonhnet/verified-node-replication.git" +revspec = "jonh/sets-typed-finite" +crate_root = "verified-node-replication/src/lib.rs" +extra_args = ["--crate-type=dylib"] diff --git a/tools/veritas/run_configuration_page_table.toml b/tools/veritas/run_configuration_page_table.toml index e91538826e..7793b6aed6 100644 --- a/tools/veritas/run_configuration_page_table.toml +++ b/tools/veritas/run_configuration_page_table.toml @@ -8,5 +8,4 @@ name = "page-table" git_url = "https://github.com/utaal/verified-nrkernel.git" revspec = "main" crate_root = "page-table/src/lib.rs" -extra_args = ["--crate-type=lib", "--rlimit", "60"] - +extra_args = ["--crate-type=lib", "--rlimit", "60", "--cfg", "feature=\"impl\""] diff --git a/tools/veritas/src/main.rs b/tools/veritas/src/main.rs index 92c32f265f..456d3c6bc3 100644 --- a/tools/veritas/src/main.rs +++ b/tools/veritas/src/main.rs @@ -2,7 +2,8 @@ const REPOS_CACHE_PATH_VAR: &str = "REPOS_CACHE_PATH"; const WORKDIR_PATH_VAR: &str = "WORKDIR_PATH"; const Z3_CACHE_PATH_VAR: &str = "Z3_CACHE_PATH"; const OUTPUT_PATH_VAR: &str = "OUTPUT_PATH"; -const RUNNER_PATH: &str = "/root/veritas"; +const RUNNER_PATH_VAR: &str = "VERITAS_RUNNER_PATH"; +const RUNNER_PATH_DEFAULT: &str = "/root/veritas"; const BUILD_VERUS_SCRIPT_FILENAME: &str = "build_verus.sh"; const GET_Z3_SCRIPT_FILENAME: &str = "get-z3.sh"; const VERUS_PROJECT_NAME: &str = "verus"; @@ -50,7 +51,10 @@ mod printing { } } -use std::{path::PathBuf, process::Command}; +use std::{ + path::{Path, PathBuf}, + process::Command, +}; #[allow(unused_imports)] use printing::{info, log_command, verror, warn}; @@ -132,6 +136,19 @@ struct ReposCache { repos_cache_path: PathBuf, } +fn runner_path() -> PathBuf { + std::env::var(RUNNER_PATH_VAR) + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from(RUNNER_PATH_DEFAULT)) +} + +fn display_path(path: &std::path::Path) -> String { + path.strip_prefix("/root") + .unwrap_or(path) + .display() + .to_string() +} + fn env_var_dir_or_err(var: &str) -> Result { let path = std::env::var(var) .map_err(|_| verror!("{} env var not set", var)) @@ -202,7 +219,7 @@ impl Z3Cache { let result = log_command( Command::new("/bin/bash") .current_dir(scratch_dir) - .arg(std::path::Path::new(RUNNER_PATH).join(GET_Z3_SCRIPT_FILENAME)) + .arg(runner_path().join(GET_Z3_SCRIPT_FILENAME)) .arg(version) .arg(&z3_path), ) @@ -218,7 +235,7 @@ struct WorkDir { } struct Checkout { - repository: git2::Repository, + workdir: PathBuf, hash: String, } @@ -248,7 +265,7 @@ impl WorkDir { .join(repo_name.to_owned() + "-" + &digest::str_digest(revspec)); let cached_repo = cache.ensure_cache_repo(repo_name, repo_url)?; - let repository = git2::Repository::init(work_path) + let repository = git2::Repository::init(&work_path) .map_err(|e| verror!("failed to init repo in work path: {}", e))?; let mut origin_remote = repository .find_remote("origin") @@ -285,7 +302,34 @@ impl WorkDir { std::mem::drop(object); std::mem::drop(reference); - Ok(Checkout { repository, hash }) + Ok(Checkout { + workdir: work_path, + hash, + }) + } + + fn checkout_local( + &mut self, + repo_name: &str, + local_path: &str, + ) -> Result { + let local_path = Path::new(local_path); + if !local_path.exists() { + return Err(verror!( + "local checkout path does not exist: {}", + local_path.display() + )); + } + let work_path = self.workdir_path.join( + repo_name.to_owned() + + "-local-" + + &digest::str_digest(local_path.to_string_lossy().as_ref()), + ); + copy_directory(local_path, &work_path)?; + Ok(Checkout { + workdir: work_path, + hash: local_checkout_hash(local_path)?, + }) } fn scratch(&mut self) -> Result { @@ -310,11 +354,216 @@ impl WorkDir { } } +fn copy_directory(src: &Path, dst: &Path) -> Result<(), VeritasError> { + if !src.is_dir() { + return Err(verror!("{} is not a directory", src.display())); + } + std::fs::create_dir(dst) + .map_err(|e| verror!("cannot create {}: {}", dst.display(), e))?; + for entry in + std::fs::read_dir(src).map_err(|e| verror!("cannot read {}: {}", src.display(), e))? + { + let entry = entry.map_err(|e| verror!("cannot read directory entry: {}", e))?; + let entry_path = entry.path(); + let entry_name = entry.file_name(); + if should_skip_local_copy_entry(&entry_name) { + continue; + } + let dst_path = dst.join(&entry_name); + let file_type = entry + .file_type() + .map_err(|e| verror!("cannot stat {}: {}", entry_path.display(), e))?; + if file_type.is_dir() { + copy_directory(&entry_path, &dst_path)?; + } else if file_type.is_symlink() { + let target = std::fs::read_link(&entry_path) + .map_err(|e| verror!("cannot read symlink {}: {}", entry_path.display(), e))?; + #[cfg(unix)] + std::os::unix::fs::symlink(&target, &dst_path) + .map_err(|e| verror!("cannot create symlink {}: {}", dst_path.display(), e))?; + #[cfg(not(unix))] + { + return Err(verror!( + "symlink copy unsupported on this platform: {}", + entry_path.display() + )); + } + } else { + std::fs::copy(&entry_path, &dst_path).map_err(|e| { + verror!( + "cannot copy {} to {}: {}", + entry_path.display(), + dst_path.display(), + e + ) + })?; + } + } + Ok(()) +} + +fn should_skip_local_copy_entry(entry_name: &std::ffi::OsStr) -> bool { + matches!( + entry_name.to_str(), + Some(".git" | ".verus-solver-log" | "target" | "target-verus") + ) +} + +fn local_checkout_hash(local_path: &Path) -> Result { + let repository = match git2::Repository::discover(local_path) { + Ok(repository) => repository, + Err(_) => { + return Ok(format!( + "local-{}", + digest::str_digest(local_path.to_string_lossy().as_ref()) + )); + } + }; + let head = repository + .head() + .ok() + .and_then(|head| head.target()) + .map(|oid| oid.to_string()) + .unwrap_or_else(|| "HEAD".to_owned()); + let mut status_options = git2::StatusOptions::new(); + status_options + .include_untracked(true) + .recurse_untracked_dirs(true); + let dirty = repository + .statuses(Some(&mut status_options)) + .map_err(|e| verror!("cannot read git status for {}: {}", local_path.display(), e))? + .iter() + .any(|entry| entry.status() != git2::Status::CURRENT); + Ok(if dirty { + format!("{head}-dirty") + } else { + head + }) +} + +fn find_verus_support_artifact(deps_dir: &Path, crate_name: &str) -> Result { + let prefix = format!("lib{crate_name}-"); + let mut matches = Vec::new(); + for entry in std::fs::read_dir(deps_dir) + .map_err(|e| verror!("cannot read {}: {}", deps_dir.display(), e))? + { + let entry = entry.map_err(|e| verror!("cannot read directory entry: {}", e))?; + let path = entry.path(); + let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else { + continue; + }; + if file_name.starts_with(&prefix) + && (file_name.ends_with(".rlib") + || file_name.ends_with(".so") + || file_name.ends_with(".dylib") + || file_name.ends_with(".dll")) + { + let modified = std::fs::metadata(&path) + .and_then(|metadata| metadata.modified()) + .unwrap_or(std::time::SystemTime::UNIX_EPOCH); + matches.push((modified, path)); + } + } + matches + .into_iter() + .max_by_key(|(modified, _path)| *modified) + .map(|(_modified, path)| path) + .ok_or_else(|| verror!("cannot find verus support artifact for {}", crate_name)) +} + +fn push_verus_support_extern( + args: &mut Vec, + deps_dir: &Path, + extern_name: &str, + crate_name: &str, +) -> Result<(), VeritasError> { + let artifact = find_verus_support_artifact(deps_dir, crate_name)?; + args.push("--extern".to_owned()); + args.push(format!("{extern_name}={}", artifact.display())); + Ok(()) +} + +fn verus_support_args(verus_binary_path: &Path) -> Result, VeritasError> { + let release_dir = verus_binary_path + .parent() + .ok_or_else(|| verror!("verus binary has no parent directory"))?; + let deps_dir = release_dir + .parent() + .and_then(|target_verus_dir| target_verus_dir.parent()) + .map(|source_dir| source_dir.join("target/release/deps")) + .filter(|deps_dir| deps_dir.is_dir()) + .unwrap_or_else(|| release_dir.join("vstd/target/debug/deps")); + if !deps_dir.is_dir() { + return Err(verror!( + "cannot find verus support deps directory {}", + deps_dir.display() + )); + } + let mut args = vec!["-L".to_owned(), format!("dependency={}", deps_dir.display())]; + push_verus_support_extern(&mut args, &deps_dir, "builtin", "verus_builtin")?; + push_verus_support_extern(&mut args, &deps_dir, "builtin_macros", "verus_builtin_macros")?; + push_verus_support_extern( + &mut args, + &deps_dir, + "state_machines_macros", + "verus_state_machines_macros", + )?; + push_verus_support_extern(&mut args, &deps_dir, "verus_builtin", "verus_builtin")?; + push_verus_support_extern( + &mut args, + &deps_dir, + "verus_builtin_macros", + "verus_builtin_macros", + )?; + push_verus_support_extern( + &mut args, + &deps_dir, + "verus_state_machines_macros", + "verus_state_machines_macros", + )?; + Ok(args) +} + +fn refresh_local_vstd(verus_workdir: &Path, verus_binary_path: &Path) -> Result<(), VeritasError> { + let verus_target_path = verus_binary_path + .parent() + .ok_or_else(|| verror!("verus binary has no parent directory"))?; + let mut cmd = Command::new("cargo"); + cmd.current_dir(verus_workdir.join("source")) + .env("VERUS_IN_VARGO", "1") + .env("RUSTC_BOOTSTRAP", "1") + .arg("run") + .arg("--locked") + .arg("--offline") + .arg("-p") + .arg("vstd_build") + .arg("--") + .arg(verus_target_path); + match verus_target_path.file_name().and_then(|name| name.to_str()) { + Some("release") => { + cmd.arg("--release"); + } + Some("debug") => {} + _ => { + warn(&format!( + "unknown verus target profile directory {}; rebuilding vstd without explicit profile flag", + verus_target_path.display() + )); + } + } + cmd.arg("--no-solver-version-check"); + let result = log_command(&mut cmd) + .status() + .map_err(|e| verror!("cannot execute vstd build helper: {}", e))?; + result.success_or_err() +} + #[derive(Debug, Serialize, Deserialize, Hash, Clone)] struct RunConfigurationProject { name: String, git_url: String, revspec: String, + local_path: Option, crate_root: String, extra_args: Option>, prepare_script: Option, @@ -328,6 +577,8 @@ fn verus_verify_vstd_default() -> bool { struct RunConfiguration { verus_git_url: String, verus_revspec: String, + verus_local_path: Option, + verus_binary_path: Option, verus_features: Vec, #[serde(default = "verus_verify_vstd_default")] verus_verify_vstd: bool, @@ -390,6 +641,7 @@ fn run(run_configuration_path: &str) -> Result<(), VeritasError> { name: "verus-vstd".to_owned(), git_url: run_configuration.verus_git_url.clone(), revspec: run_configuration.verus_revspec.clone(), + local_path: run_configuration.verus_local_path.clone(), crate_root: "source/vstd/vstd.rs".to_owned(), extra_args: Some(vec![ "--is-vstd".to_owned(), @@ -409,66 +661,92 @@ fn run(run_configuration_path: &str) -> Result<(), VeritasError> { let mut workdir = WorkDir::init()?; let mut z3_cache = Z3Cache::init()?; - info(&format!( - "checking out verus {}", - run_configuration.verus_revspec - )); - let verus_checkout = workdir.checkout( - &mut repos_cache, - VERUS_PROJECT_NAME, - &run_configuration.verus_git_url, - &run_configuration.verus_revspec, - )?; - info(&format!("checked out verus commit {}", verus_checkout.hash)); - info("building verus"); - let verus_workdir = verus_checkout - .repository - .workdir() - .expect("no workdir in work repository"); - let z3_version = { - let get_z3_src = std::fs::read_to_string(verus_workdir.join("source/tools/get-z3.sh")) - .map_err(|e| verror!("cannot read get-z3.sh: {}", e))?; - let z3_version_regex = - regex::Regex::new(r#"z3_version="([^"]+)""#).expect("invalid regex for z3_version"); - z3_version_regex - .captures(&get_z3_src) - .ok_or_else(|| verror!("cannot find z3_version in get_z3.sh"))? - .get(1) - .expect("no capture group") - .as_str() - .to_string() - }; - info(&format!("getting z3 {z3_version}")); - let z3_cached = z3_cache.ensure_z3_version(&mut workdir, &z3_version)?; - std::fs::copy(&z3_cached, verus_workdir.join("source/z3")) - .map_err(|e| verror!("cannot copy z3 to verus source: {}", e))?; - - let features_args = if run_configuration.verus_features.len() > 0 { - Some("--features".to_string()) - .into_iter() - .chain(run_configuration.verus_features.iter().cloned()) - .collect::>() - .join(" ") + let verus_checkout = if let Some(local_path) = &run_configuration.verus_local_path { + info(&format!("copying local verus from {}", local_path)); + workdir.checkout_local(VERUS_PROJECT_NAME, local_path)? } else { - String::new() + info(&format!( + "checking out verus {}", + run_configuration.verus_revspec + )); + workdir.checkout( + &mut repos_cache, + VERUS_PROJECT_NAME, + &run_configuration.verus_git_url, + &run_configuration.verus_revspec, + )? }; + info(&format!("checked out verus commit {}", verus_checkout.hash)); + let verus_workdir = verus_checkout.workdir.as_path(); + let (verus_build_duration, verus_binary_path) = + if let Some(verus_binary_path) = &run_configuration.verus_binary_path { + let verus_binary_path = PathBuf::from(verus_binary_path); + if !verus_binary_path.exists() { + return Err(verror!( + "configured verus binary does not exist: {}", + verus_binary_path.display() + )); + } + info(&format!( + "using existing verus binary {}", + verus_binary_path.display() + )); + if run_configuration.verus_local_path.is_some() { + info("refreshing bundled vstd from the checked-out local verus source"); + refresh_local_vstd(verus_workdir, &verus_binary_path)?; + } + (std::time::Duration::ZERO, verus_binary_path) + } else { + info("building verus"); + let z3_version = { + let get_z3_src = + std::fs::read_to_string(verus_workdir.join("source/tools/get-z3.sh")) + .map_err(|e| verror!("cannot read get-z3.sh: {}", e))?; + let z3_version_regex = regex::Regex::new(r#"z3_version="([^"]+)""#) + .expect("invalid regex for z3_version"); + z3_version_regex + .captures(&get_z3_src) + .ok_or_else(|| verror!("cannot find z3_version in get_z3.sh"))? + .get(1) + .expect("no capture group") + .as_str() + .to_string() + }; + info(&format!("getting z3 {z3_version}")); + let z3_cached = z3_cache.ensure_z3_version(&mut workdir, &z3_version)?; + std::fs::copy(&z3_cached, verus_workdir.join("source/z3")) + .map_err(|e| verror!("cannot copy z3 to verus source: {}", e))?; + + let features_args = if run_configuration.verus_features.len() > 0 { + Some("--features".to_string()) + .into_iter() + .chain(run_configuration.verus_features.iter().cloned()) + .collect::>() + .join(" ") + } else { + String::new() + }; - let verus_build_start = std::time::Instant::now(); - let result = log_command( - Command::new("/bin/bash") - .current_dir(verus_workdir) - .env("VERUS_FEATURES_ARGS", &features_args) - .arg(std::path::Path::new(RUNNER_PATH).join(BUILD_VERUS_SCRIPT_FILENAME)), - ) - .status() - .map_err(|e| verror!("cannot execute verus build script: {}", e))?; - result.success_or_err()?; - let verus_build_duration = verus_build_start.elapsed(); + let verus_build_start = std::time::Instant::now(); + let result = log_command( + Command::new("/bin/bash") + .current_dir(verus_workdir) + .env("VERUS_FEATURES_ARGS", &features_args) + .arg(runner_path().join(BUILD_VERUS_SCRIPT_FILENAME)), + ) + .status() + .map_err(|e| verror!("cannot execute verus build script: {}", e))?; + result.success_or_err()?; - info("verus ready"); - let verus_binary_path = verus_workdir.join("source/target-verus/release/verus"); + info("verus ready"); + ( + verus_build_start.elapsed(), + verus_workdir.join("source/target-verus/release/verus"), + ) + }; // TODO perform line counting? (once line_count is fixed) // let _verus_line_count_path = verus_workdir.join("source/target/release/line_count"); + let verus_support_args = verus_support_args(&verus_binary_path)?; let output_path = env_var_dir_or_err(OUTPUT_PATH_VAR)?; let date = chrono::Utc::now() @@ -493,20 +771,22 @@ fn run(run_configuration_path: &str) -> Result<(), VeritasError> { info("running projects"); for project in run_configuration.projects.iter() { info(&format!("running project {}", project.name)); - let proj_checkout = workdir.checkout( - &mut repos_cache, - &project.name, - &project.git_url, - &project.revspec, - )?; - let proj_workdir = proj_checkout - .repository - .workdir() - .expect("no workdir in work repository"); + let proj_checkout = if let Some(local_path) = &project.local_path { + workdir.checkout_local(&project.name, local_path)? + } else { + workdir.checkout( + &mut repos_cache, + &project.name, + &project.git_url, + &project.revspec, + )? + }; + let proj_workdir = proj_checkout.workdir.as_path(); if let Some(prepare_script) = &project.prepare_script { let result = log_command( Command::new("/bin/bash") .current_dir(proj_workdir) + .env_remove("CARGO_TARGET_DIR") .arg("-c") .arg(prepare_script), ) @@ -522,6 +802,11 @@ fn run(run_configuration_path: &str) -> Result<(), VeritasError> { .current_dir(proj_workdir) .arg(&project.crate_root) .args(project.extra_args.as_ref().map(|ea| &ea[..]).unwrap_or(&[])) + .args(if project.name == "verus-vstd" { + &[][..] + } else { + &verus_support_args[..] + }) .arg("--output-json") .arg("--time") .arg("--no-report-long-running"), @@ -709,17 +994,11 @@ fn run(run_configuration_path: &str) -> Result<(), VeritasError> { .map_err(|e| verror!("cannot write summary toml: {}", e))?; info(&format!( "output written to {}", - run_output_path - .strip_prefix("/root") - .expect("/root prefix") - .display() + display_path(&run_output_path) )); info(&format!( "summary written to {}", - summary_output_path - .strip_prefix("/root") - .expect("/root prefix") - .display() + display_path(&summary_output_path) )); { @@ -808,10 +1087,7 @@ fn run(run_configuration_path: &str) -> Result<(), VeritasError> { std::mem::drop(summary_md_file); info(&format!( "markdown table written to {}", - summary_md_output_path - .strip_prefix("/root") - .expect("/root prefix") - .display() + display_path(&summary_md_output_path) )); }