use super::super::prelude::*; use super::super::view::View; use super::cmp::{PartialOrdIs, PartialOrdSpec}; use super::iter::{IteratorSpec, StepSpec, StepSpecImpl}; use core::ops::{ Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, }; verus! { #[verifier::external_type_specification] #[verifier::reject_recursive_types_in_ground_variants(Idx)] pub struct ExRange(Range); #[verifier::external_type_specification] #[verifier::external_body] #[verifier::reject_recursive_types_in_ground_variants(Idx)] pub struct ExRangeInclusive(RangeInclusive); pub struct RangeInclusiveView { pub start: Idx, pub end: Idx, pub exhausted: bool, } pub trait ContainsSpec where Idx: PartialOrd, U: ?Sized + PartialOrd { spec fn obeys_contains() -> bool; spec fn contains_spec(&self, i: &U) -> bool; } impl ContainsSpec for RangeInclusive where Idx: PartialOrd, U: ?Sized + PartialOrd, { open spec fn obeys_contains() -> bool { (U::obeys_partial_cmp_spec() && >::obeys_partial_cmp_spec()) } open spec fn contains_spec(&self, i: &U) -> bool { self@.start.is_le(&i) && if self@.exhausted { i.is_lt(&self@.end) } else { i.is_le(&self@.end) } } } impl ContainsSpec for Range where Idx: PartialOrd, U: ?Sized + PartialOrd, { open spec fn obeys_contains() -> bool { (U::obeys_partial_cmp_spec() && >::obeys_partial_cmp_spec()) } open spec fn contains_spec(&self, i: &U) -> bool { self.start.is_le(&i) && i.is_lt(&self.end) } } impl View for RangeInclusive { type V = RangeInclusiveView; uninterp spec fn view(&self) -> Self::V; } pub uninterp spec fn spec_range_next(a: Range) -> (Range, Option); /// Whether `A`'s executable stepping, comparison, and cloning obey the model /// used by range iterators. The axioms below establish this for primitive /// integers only; implementing `StepSpecImpl` alone does not establish it. pub uninterp spec fn obeys_range_step_spec() -> bool; /// Range::contains method is valid and safe to use only when cmp operations are implemented to satisfy /// obeys_partial_cmp_spec. Specifically, the comparison must be deterministic, and `lt` (less than) /// and `le` (less than or equal to) must define total orders. /// If using Range::contains with types that do not satisfy obeys_partial_cmp_spec, no spec is provided. pub assume_specification, U>[ Range::::contains ]( r: &Range, i: &U, ) -> (ret: bool) where Idx: PartialOrd, U: ?Sized + PartialOrd ensures as ContainsSpec>::obeys_contains() ==> ret == r.contains_spec(i), ; pub assume_specification, U>[ RangeInclusive::::contains ]( r: &RangeInclusive, i: &U, ) -> (ret: bool) where Idx: PartialOrd, U: ?Sized + PartialOrd ensures as ContainsSpec>::obeys_contains() ==> ret == r.contains_spec(i), ; // A range is empty once its iterator is exhausted, or if it was never valid // to begin with (start > end). pub open spec fn spec_range_inclusive_is_empty>( r: &RangeInclusive, ) -> bool { !r@.start.is_le(&r@.end) || r@.exhausted } pub assume_specification>[ RangeInclusive::::is_empty ]( r: &RangeInclusive, ) -> (res: bool) where Idx: PartialOrd ensures >::obeys_partial_cmp_spec() ==> res == spec_range_inclusive_is_empty(r), ; pub assume_specification[ RangeInclusive::::new ](start: Idx, end: Idx) -> (ret: core::ops::RangeInclusive) ensures ret@ == (RangeInclusiveView { start, end, exhausted: false }), ; impl super::iter::IteratorSpecImpl for Range { open spec fn obeys_prophetic_iter_laws(&self) -> bool { obeys_range_step_spec::() } open spec fn remaining(&self) -> Seq { let steps = self.start.spec_steps_between_int(self.end); let len = if steps > 0 { steps } else { 0 }; Seq::new(len as nat, |i: int| self.start.spec_forward_checked_int(i).unwrap()) } #[verifier::prophetic] open spec fn produces(&self) -> Seq { Seq::new(IteratorSpec::remaining(self).len(), |i: int| true) } open spec fn decrease(&self) -> Option { Some(self.start.spec_steps_between_int(self.end) as nat) } open spec fn peek(&self, index: int) -> Option { //Some(self.start.spec_forward_checked_int(index).unwrap()) if 0 <= index <= self.start.spec_steps_between_int(self.end) { Some(self.start.spec_forward_checked_int(index).unwrap()) } else { None } } } /// Number of elements left in an inclusive range: zero once it is exhausted or if `start > end`. pub open spec fn spec_range_inclusive_len(r: &RangeInclusive) -> nat { let steps = r@.start.spec_steps_between_int(r@.end); if steps >= 0 && !r@.exhausted { (steps + 1) as nat } else { 0 } } impl super::iter::IteratorSpecImpl for RangeInclusive { open spec fn obeys_prophetic_iter_laws(&self) -> bool { obeys_range_step_spec::() } open spec fn remaining(&self) -> Seq { Seq::new( spec_range_inclusive_len(self), |i: int| self@.start.spec_forward_checked_int(i).unwrap(), ) } #[verifier::prophetic] open spec fn produces(&self) -> Seq { Seq::new(IteratorSpec::remaining(self).len(), |i: int| true) } open spec fn decrease(&self) -> Option { Some(spec_range_inclusive_len(self)) } open spec fn peek(&self, index: int) -> Option { if 0 <= index <= spec_range_inclusive_len(self) { Some(self@.start.spec_forward_checked_int(index).unwrap()) } else { None } } } pub assume_specification[ as Iterator>::next ]( range: &mut Range, ) -> (r: Option) ensures obeys_range_step_spec::() ==> (*final(range), r) == spec_range_next(*old(range)), ; #[verifier::external_type_specification] pub struct ExBound(Bound); #[verifier::external_type_specification] pub struct ExRangeFull(RangeFull); #[verifier::external_type_specification] #[verifier::reject_recursive_types(Idx)] pub struct ExRangeFrom(RangeFrom); #[verifier::external_type_specification] #[verifier::reject_recursive_types(Idx)] pub struct ExRangeTo(RangeTo); #[verifier::external_type_specification] #[verifier::reject_recursive_types(Idx)] pub struct ExRangeToInclusive(RangeToInclusive); pub open spec fn bound_as_ref(b: &Bound) -> Bound<&T> { match b { Bound::Included(start) => Bound::Included(start), Bound::Excluded(start) => Bound::Excluded(start), Bound::Unbounded => Bound::Unbounded, } } // Per-type specifications for `RangeBounds::start_bound`/`end_bound`. Each spec // agrees with the corresponding `RangeBoundsSpecImpl`, and so with the // `ExRangeBounds` ensures below. pub assume_specification<'s, T>[ as RangeBounds>::start_bound ]( range: &'s Range, ) -> (result: Bound<&'s T>) ensures result == Bound::Included(&range.start), ; pub assume_specification<'s, T>[ as RangeBounds>::end_bound ]( range: &'s Range, ) -> (result: Bound<&'s T>) ensures result == Bound::Excluded(&range.end), ; pub assume_specification<'s, T: ?Sized>[ >::start_bound ]( range: &'s RangeFull, ) -> (result: Bound<&'s T>) ensures result == Bound::Unbounded, ; pub assume_specification<'s, T: ?Sized>[ >::end_bound ]( range: &'s RangeFull, ) -> (result: Bound<&'s T>) ensures result == Bound::Unbounded, ; pub assume_specification<'s, T>[ as RangeBounds>::start_bound ]( range: &'s RangeFrom, ) -> (result: Bound<&'s T>) ensures result == Bound::Included(&range.start), ; pub assume_specification<'s, T>[ as RangeBounds>::end_bound ]( range: &'s RangeFrom, ) -> (result: Bound<&'s T>) ensures result == Bound::Unbounded, ; pub assume_specification<'s, T>[ as RangeBounds>::start_bound ]( range: &'s RangeTo, ) -> (result: Bound<&'s T>) ensures result == Bound::Unbounded, ; pub assume_specification<'s, T>[ as RangeBounds>::end_bound ]( range: &'s RangeTo, ) -> (result: Bound<&'s T>) ensures result == Bound::Excluded(&range.end), ; pub assume_specification<'s, T>[ as RangeBounds>::start_bound ]( range: &'s RangeInclusive, ) -> (result: Bound<&'s T>) ensures result == Bound::Included(&range@.start), ; // Shared with `RangeBoundsSpecImpl::spec_end_bound` below, so the two can't // drift apart: `end_bound()` returns `Included` while the range is not // exhausted and `Excluded` after it is exhausted. pub open spec fn spec_range_inclusive_end_bound(r: &RangeInclusive) -> Bound<&T> { if r@.exhausted { Bound::Excluded(&r@.end) } else { Bound::Included(&r@.end) } } pub assume_specification<'s, T>[ as RangeBounds>::end_bound ]( range: &'s RangeInclusive, ) -> (result: Bound<&'s T>) ensures result == spec_range_inclusive_end_bound(range), ; pub assume_specification<'s, T>[ as RangeBounds>::start_bound ]( range: &'s RangeToInclusive, ) -> (result: Bound<&'s T>) ensures result == Bound::Unbounded, ; pub assume_specification<'s, T>[ as RangeBounds>::end_bound ]( range: &'s RangeToInclusive, ) -> (result: Bound<&'s T>) ensures result == Bound::Included(&range.end), ; pub assume_specification<'s, T>[ <(Bound, Bound) as RangeBounds>::start_bound ]( range: &'s (Bound, Bound), ) -> (result: Bound<&'s T>) ensures result == bound_as_ref(&range.0), ; pub assume_specification<'s, T>[ <(Bound, Bound) as RangeBounds>::end_bound ]( range: &'s (Bound, Bound), ) -> (result: Bound<&'s T>) ensures result == bound_as_ref(&range.1), ; /// Specification for [`core::ops::RangeBounds`], exposing spec-mode models /// [`spec_start_bound`](RangeBoundsSpec::spec_start_bound) and /// [`spec_end_bound`](RangeBoundsSpec::spec_end_bound) of the trait's /// `start_bound`/`end_bound` methods. This mirrors std's normalization of an /// arbitrary range into a pair of bounds and is the model used by /// `<[T]>::copy_within` (see `vstd::std_specs::slice`). Implementations must /// return these spec bounds from `start_bound`/`end_bound`, since std's /// `copy_within` calls those methods. #[verifier::external_trait_specification] #[verifier::external_trait_extension(RangeBoundsSpec via RangeBoundsSpecImpl)] pub trait ExRangeBounds { type ExternalTraitSpecificationFor: RangeBounds; spec fn spec_start_bound(&self) -> Bound<&T>; spec fn spec_end_bound(&self) -> Bound<&T>; fn start_bound(&self) -> (r: Bound<&T>) ensures r == self.spec_start_bound(), ; fn end_bound(&self) -> (r: Bound<&T>) ensures r == self.spec_end_bound(), ; } impl RangeBoundsSpecImpl for Range { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Included(&self.start) } open spec fn spec_end_bound(&self) -> Bound<&T> { Bound::Excluded(&self.end) } } impl RangeBoundsSpecImpl for RangeFull { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Unbounded } open spec fn spec_end_bound(&self) -> Bound<&T> { Bound::Unbounded } } impl RangeBoundsSpecImpl for RangeFrom { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Included(&self.start) } open spec fn spec_end_bound(&self) -> Bound<&T> { Bound::Unbounded } } impl RangeBoundsSpecImpl for RangeTo { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Unbounded } open spec fn spec_end_bound(&self) -> Bound<&T> { Bound::Excluded(&self.end) } } impl RangeBoundsSpecImpl for RangeInclusive { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Included(&self@.start) } open spec fn spec_end_bound(&self) -> Bound<&T> { spec_range_inclusive_end_bound(self) } } impl RangeBoundsSpecImpl for RangeToInclusive { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Unbounded } open spec fn spec_end_bound(&self) -> Bound<&T> { Bound::Included(&self.end) } } impl RangeBoundsSpecImpl for (Bound, Bound) { open spec fn spec_start_bound(&self) -> Bound<&T> { bound_as_ref(&self.0) } open spec fn spec_end_bound(&self) -> Bound<&T> { bound_as_ref(&self.1) } } impl<'a, T: ?Sized + 'a> RangeBoundsSpecImpl for (Bound<&'a T>, Bound<&'a T>) { open spec fn spec_start_bound(&self) -> Bound<&T> { self.0 } open spec fn spec_end_bound(&self) -> Bound<&T> { self.1 } } impl RangeBoundsSpecImpl for RangeFrom<&T> { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Included(self.start) } open spec fn spec_end_bound(&self) -> Bound<&T> { Bound::Unbounded } } impl RangeBoundsSpecImpl for RangeTo<&T> { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Unbounded } open spec fn spec_end_bound(&self) -> Bound<&T> { Bound::Excluded(self.end) } } impl RangeBoundsSpecImpl for Range<&T> { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Included(self.start) } open spec fn spec_end_bound(&self) -> Bound<&T> { Bound::Excluded(self.end) } } impl RangeBoundsSpecImpl for RangeInclusive<&T> { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Included(self@.start) } open spec fn spec_end_bound(&self) -> Bound<&T> { // In contrast to RangeBounds for RangeInclusive, // Rust's RangeBounds for RangeInclusive<&T> always returns Included, // regardless of exhausted. Bound::Included(self@.end) } } impl RangeBoundsSpecImpl for RangeToInclusive<&T> { open spec fn spec_start_bound(&self) -> Bound<&T> { Bound::Unbounded } open spec fn spec_end_bound(&self) -> Bound<&T> { Bound::Included(self.end) } } /// Normalized (inclusive) start index of `range`, matching std's /// `core::slice::range`: an inclusive bound `i` stays `i`, an exclusive bound /// `i` becomes `i + 1`, and an unbounded start is `0`. pub open spec fn slice_range_start>(range: &R) -> int { match range.spec_start_bound() { Bound::Included(i) => *i as int, Bound::Excluded(i) => (*i as int) + 1, Bound::Unbounded => 0, } } /// Normalized (exclusive) end index of a range over a sequence of length `len`, /// matching std's `core::slice::range`: an inclusive bound `i` becomes `i + 1`, /// an exclusive bound `i` stays `i`, and an unbounded end is `len`. pub open spec fn slice_range_end>(range: &R, len: nat) -> int { match range.spec_end_bound() { Bound::Included(i) => (*i as int) + 1, Bound::Excluded(i) => *i as int, Bound::Unbounded => len as int, } } /// The bounds with which std's `SliceIndex` impls for `RangeInclusive` index: /// `start..end + 1`, or the empty range `end + 1..end + 1` if the range is exhausted /// (unlike `end_bound()`, which reports `Excluded(end)` for an exhausted range). pub open spec fn spec_range_inclusive_slice_bounds(r: &RangeInclusive) -> ( Bound, Bound, ) { ( if r@.exhausted { Bound::Excluded(r@.end) } else { Bound::Included(r@.start) }, Bound::Included(r@.end), ) } /// Whether a range normalizes to `start <= end <= len`, i.e. the condition /// under which std's `core::slice::range` does not panic. pub open spec fn slice_range_valid>(range: &R, len: nat) -> bool { slice_range_start(range) <= slice_range_end(range, len) <= len } } // verus! macro_rules! step_specs { ($t: ty, $axiom: ident, $step_axiom: ident) => { verus! { impl StepSpecImpl for $t { open spec fn spec_is_lt(self, other: Self) -> bool { self < other } open spec fn spec_steps_between(self, end: Self) -> Option { let n = end - self; if usize::MIN <= n <= usize::MAX { Some(n as usize) } else { None } } open spec fn spec_steps_between_int(self, end: Self) -> int { end - self } open spec fn spec_forward_checked(self, count: usize) -> Option { StepSpec::spec_forward_checked_int(self, count as int) } open spec fn spec_forward_checked_int(self, count: int) -> Option { if self + count <= $t::MAX { Some((self + count) as $t) } else { None } } open spec fn spec_backward_checked(self, count: usize) -> Option { StepSpec::spec_backward_checked_int(self, count as int) } open spec fn spec_backward_checked_int(self, count: int) -> Option { if self - count >= $t::MIN { Some((self - count) as $t) } else { None } } } pub broadcast proof fn $step_axiom() ensures #[trigger] obeys_range_step_spec::<$t>(), { admit(); } // TODO: we might be able to make this generic over A: StepSpec // once we settle on a way to connect std traits like Step with spec traits like StepSpec. pub broadcast proof fn $axiom(range: Range<$t>) ensures StepSpec::spec_is_lt(range.start, range.end) ==> // TODO (not important): use new "matches ==>" syntax here (if let Some(n) = StepSpec::spec_forward_checked(range.start, 1) { spec_range_next(range) == (Range { start: n, ..range }, Some(range.start)) } else { true }), !StepSpec::spec_is_lt(range.start, range.end) ==> #[trigger] spec_range_next(range) == (range, None::<$t>), { admit(); } } // verus! }; } step_specs!(u8, axiom_spec_range_next_u8, axiom_u8_obeys_range_step_spec); step_specs!(u16, axiom_spec_range_next_u16, axiom_u16_obeys_range_step_spec); step_specs!(u32, axiom_spec_range_next_u32, axiom_u32_obeys_range_step_spec); step_specs!(u64, axiom_spec_range_next_u64, axiom_u64_obeys_range_step_spec); step_specs!(u128, axiom_spec_range_next_u128, axiom_u128_obeys_range_step_spec); step_specs!(usize, axiom_spec_range_next_usize, axiom_usize_obeys_range_step_spec); step_specs!(i8, axiom_spec_range_next_i8, axiom_i8_obeys_range_step_spec); step_specs!(i16, axiom_spec_range_next_i16, axiom_i16_obeys_range_step_spec); step_specs!(i32, axiom_spec_range_next_i32, axiom_i32_obeys_range_step_spec); step_specs!(i64, axiom_spec_range_next_i64, axiom_i64_obeys_range_step_spec); step_specs!(i128, axiom_spec_range_next_i128, axiom_i128_obeys_range_step_spec); step_specs!(isize, axiom_spec_range_next_isize, axiom_isize_obeys_range_step_spec); verus! { pub broadcast group group_range_axioms { axiom_u8_obeys_range_step_spec, axiom_u16_obeys_range_step_spec, axiom_u32_obeys_range_step_spec, axiom_u64_obeys_range_step_spec, axiom_u128_obeys_range_step_spec, axiom_usize_obeys_range_step_spec, axiom_i8_obeys_range_step_spec, axiom_i16_obeys_range_step_spec, axiom_i32_obeys_range_step_spec, axiom_i64_obeys_range_step_spec, axiom_i128_obeys_range_step_spec, axiom_isize_obeys_range_step_spec, axiom_spec_range_next_u8, axiom_spec_range_next_u16, axiom_spec_range_next_u32, axiom_spec_range_next_u64, axiom_spec_range_next_u128, axiom_spec_range_next_usize, axiom_spec_range_next_i8, axiom_spec_range_next_i16, axiom_spec_range_next_i32, axiom_spec_range_next_i64, axiom_spec_range_next_i128, axiom_spec_range_next_isize, } } // verus!