# Associated Predicates
## Problem: Specify bounds checking for `Vec` accesses?
Suppose that `v : Vec<T>` and suppose that we want to verify `... v[i]...`, which gets translated into the MIR
```rust
let s = Deref::deref(&v);
let x = Index::index(s, i);
```
So, the "bounds check" must be specified by refinements for `Deref::deref` and `Index::index`.
### 1. Spec for `Deref` and `Index`
```rust
impl<T> Deref for Vec<T> {
type Output = [T];
#[flux::sig(fn deref(&Vec<T>[@n]) -> &[T][n])]
fn deref(&self) -> &[T] {
...
}
}
```
Sadly, `Index::index` is defined in a *generic* fashion for any `I` that implements `SliceIndex`, so the "bounds check" needs to be abstracted over any such `I`.
```rust
impl<T, I> Index<I> for [T]
where
I: SliceIndex<[T]>
{
type Output = I::Output
#[flux::sig(&[T][@n], I{v: <I as SliceIndex<[T]>>::contained(v, n)})]
fn index(&self, idx: I) -> &Self::Output {
idx.get(self)
}
}
```
### 2. Associate Predicate with Trait `SliceIndex`
Here `contained` is an **associated predicate** of the trait `SliceIndex`
```rust
#[flux::generics(Self as base)]
#[flux::predicate{contained: (Self, T) -> bool}]
pub unsafe trait SliceIndex<T as base>: Sealed where T: ?Sized { ... }
```
and we can (finally) define the bounds check by defining `contained` for the `impl` of `SliceIndex` for `usize`
```rust
#[flux::predicate{contained : |idx, n| idx < n}]
impl<T> SliceIndex<[T]> for usize {...}
```
## Sound Version: Predicate on `MyTrait`
```rust
trait MyTrait {
predicate f: (Self) -> bool
#[flux::sig({Self[@n] | <Self as MyTrait>::f(n)}) -> i32]
fn method(&self) -> i32;
}
impl MyTrait for i32 {
predicate f = |x| x > 0;
#[flux::sig({Self[@n] | <Self as MyTrait>::f(n)}) -> i32]
#[flux::sig({Self[@n] | n > 0}) -> i32]
fn method(&self) -> i32 {
0
}
}
fn test(x: i32) {
x.method() // fail because we don't know x > 0
}
```
## Unsound Version: Predicate on `impl MyTrait for i32`
```rust
trait MyTrait {
fn method(&self) -> i32;
}
impl MyTrait for i32 {
predicate f = |x| x > 0;
#[flux::sig({Self[@n] | <Self as MyTrait>::f(n)}) -> i32]
fn method(&self) -> i32 {
0
}
}
fn test(x: i32) {
x.method() // fail because we don't know x > 0
}
```
## Associated Predicates
### Step 1: Refinements on `Self`
**nico(12/28/23):** just realized this example shouldn't work as is because the signature of `test` forgets the abstract refinement and we shouldn't create a kvar for `T` because it's bounded
```rust
#[flux::generics(Self as base)]
trait MyTrait {
#[flux::sig(fn<refine p: Self -> bool>(&Self{v: p(v)}) -> Self{v: p(v)})]
fn method(&self) -> Self;
}
impl MyTrait for i32 {
#[flux::sig(fn<refine p: Self -> bool>(&Self{v: p(v)}) -> Self{v: p(v)})]
fn method(&self) -> Self {
*self
}
}
fn test<T: MyTrait>(x: T) -> T {
x.method()
}
fn client() {
assert(test(0) == 0);
}
```
**rj (12/29/23):** Ok, instead I've gotten this working.
```rust
#[flux::generics(Self as base)]
pub trait MyTrait {
#[flux::sig(fn<refine p: Self -> bool>(&Self{v: p(v)}) -> Self{v: p(v)})]
fn foo(&self) -> Self;
}
#[flux::sig(fn<T as base, refine q: T -> bool> (&T{v:q(v)}) -> T{v: q(v)})]
pub fn bar<T: MyTrait>(x: &T) -> T {
x.foo()
}
```
### Step 2: Associate Predicates with `trait`
```rust
#[flux::generics(Self as base)]
#[flux::predicate{ f : (Self) -> bool }]
trait MyTrait {
fn method(&Self) -> i32;
}
#[flux::predicate{ f : |x| { 0 < x } }]
impl MyTrait for i32 {
fn method(&Self) -> i32 { ... }
}
#[flux::sig({&T[@x] | <T as MyTrait>::f(x)})]
fn bob<T: MyTrait>(x: &T) {
x.method()
}
fn test(){
let z0 = 0;
bob(&z0); // reject!
let z1 = 1;
bob(&z1); // accept
}
```
### Step 3: Use predicates on Trait Methods
* `v1` put sig on `impl`
* `v2` put sig on `trait`
```rust
// #[flux::predicate(f: (Self) -> bool)];
//could use 'predicate' or similar, but I like the sound of 'refined_by'
#[flux::predicate{ f : (Self) -> bool }]
trait MyTrait {
// (v2) #[flux::sig({&Self[@n] | <Self as MyTrait>::f(n)}) -> i32]
fn method(&Self) -> i32;
}
#[flux::predicate{ f : |x| { 0 < x } }]
impl MyTrait for i32 {
// (v1) #[flux::sig({&Self[@n] | <Self as MyTrait>::f(n)}) -> i32]
fn method(&Self) -> i32 { ... }
}
#[flux::sig({&T[@x] | <T as MyTrait>::f(x)})]
fn bob<T: MyTrait>(x: &T) {
...
// (v2) check this call
x.method()
...
}
#[flux::sig({&i32[@x] | 0 < x})]
fn bob1(x: &i32) {
// (v1) check this call
x.method()
}
fn test(){
let z0 = 0;
bob(&z0); // (v0) reject!
let z1 = 1;
bob(&z1); // (v0) accept
}
```
## Variant: Use Associated Type (Alias)
The trouble with the associated predicate is that it pulls all of `Ty` or `RTy` into the `Expr` language if we are to properly represent `<T as MyTrait>::f(x)` as a refinement.
Instead, we might use a **type alias** like so:
```rust
#[flux::generics(Self as base)]
#[flux::alias(type MyTy)] // 1. specify an associated type MyTy (with no params)
trait MyTrait {
fn method(&Self) -> i32;
}
#[flux::alias(type MyTy = &Self{v: 0 < v})] // 2. implement the associated type
impl MyTrait for i32 {
fn method(&Self) -> i32 { ... }
}
#[flux::sig(fn (<T as MyTrait>::MyTy))] // 3. use the associated type
fn bob<T: MyTrait>(x: &T) {
x.method()
}
fn test(){
let z0 = 0;
bob(&z0); // reject!
let z1 = 1;
bob(&z1); // accept
}
```
## Vec/Index with Associated Type
**Step 1** Specify an associated type Valid (with param n:int)
```rust=
#[flux::alias(type Valid(n:int))]
pub unsafe trait SliceIndex<T>: Sealed
where
T: ?Sized,
{ ... }
```
**Step 2** Implement `Valid` for `usize` and `SliceIndex<[T]>`
```rust=
#[flux::alias(type Valid(n:int) = Self{v:v < n})]
impl<T> SliceIndex<[T]> for usize {
type Output: ?Sized;
}
```
**Step 3** Use `Valid` to specify bounds on `Index::index`
```rust
impl<T, I> Index<I> for [T]
where
I: SliceIndex<[T]>
{
type Output = I::Output
#[flux::sig(fn(&Self[@n], idx:<I as SliceIndex<[T]>::Valid(n)))] -> &Self::Output
fn index(&self, idx: I) -> &Self::Output {
idx.get(self)
}
}
```
## Associated Predicates via Abstract Refinements
### Step 0: New concrete syntax for predicate application.
In LH you can write `T<p>` to mean `T{v: p(v)}`.
This generalizes to multiple parameters too so something like
`T<p x>` which means `T{v: p(x,v)}`
What is a good syntax for this in Flux where you don't have partial application?
*Default Option* just use the same as for existential types so
T{v:<T as Trait>::p(v)}
T{v:<T as Trait>::p(x, v)}
(Lets stick with the above for now, bit verbose but whatever.)
### Step 1: Define Projection-Predicates
```rust
struct ProjPred {
trait_id: DefId,
generic_args: GenericArgs,
pred: Symbol,
}
```
So `<T as MyTrait>::f` is represented as `proj_pred` defined as
```rust
let proj_pred = ProjPred { trait_id: MyTrait, generic_args: [T], pred: "f" }
```
### Step 2: Extend `rty::TyKind` with a proj-pred-application
```rust
struct ProjApp {
base: BaseTy,
pred: ProjPred,
args: RefineArgs,
}
pub enum TyKind {
Indexed(BaseTy, Expr),
Exists(Binder<Ty>),
Constr(Expr, Ty),
App(Binder<ProjApp>),
...
}
```
So
```rust
T{v:<T as MyTrait>::p(x, v)}
```
is represented as
```rust
App(Binder(ProjApp{base: T, pred: proj_pred, args:[x, #0]}))
```
### Step 3: Pred-App instantiation
When a predicate is "instantiated" e.g. when you instantiate `T` as `i32` and you have an `impl`
```rust
#[flux::predicate{ f : |a, b| { b < a } }]
impl MyTrait for i32 {...}
```
then you instantiate the type
```rust
T{v:<T as MyTrait>::p(x, v)}
```
represented as
```rust
App(Binder(ProjApp{base: T, pred: proj_pred, args:[x, #0]}))
```
to
```rust
Exists(Binder(Constr(#0 < x, i32[#0])))
```
where the (concrete) `proj_pred` params `[a,b]` are substituted by `[x,v]`, that is
1. The `base` becomes `i32[#0]` or `Indexed(i32, #0)` and
2. The `pred`, and `args` becomes `i32::f` with substitution `[a := x, b := #0]`
### Full Example
```rust
// Step 1 : declare -------------------------------
#[flux::generics(Self as base)]
#[flux::predicate{ f : (Self) -> bool }]
trait MyTrait {
fn method(&Self) -> i32;
}
// Step 2 : abstract ------------------------------
#[flux::sig({&T[@x] | <T as MyTrait>::f(x)})]
fn bob<T: MyTrait>(x: &T) {
x.method()
}
// Step 3 : implement -----------------------------
#[flux::predicate{ f : |x| { 0 < x } }]
impl MyTrait for i32 {
fn method(&Self) -> i32 { ... }
}
// Step 4 : concretize ----------------------------
fn test(){
let z0 = 0;
bob(&z0); // reject!
let z1 = 1;
bob(&z1); // accept
}
```
## Nico(01/08/24):
I was thinking a different representation. I'd represent it like so
```rust
// This is analogous to `AliasTy` so I'd call it `AliasPred`
// instead of `ProjPred`
pub enum AliasPred {
trait_id: DefId,
name: Symbol,
generic_args: GenericArgs,
}
pub enum TyKind {
...
Constr(Pred, Ty),
...
}
pub enum Pred {
Expr(Expr),
Alias(AliasPred, RefineArgs)
}
```
Then for your example above we represent `{T[x] | <T as MyTrait>::f(x)}` as
```rust
TyKind::Constr(Pred::Alias(AliasPred { .. }, List(x)), T[x])
```
And if we know `T` is `i32` we can normalize it to
```rust
TyKind::Constr(Pred::Expr(0 < x), i32[x])
```
Alternatively, since `<T as MyTrait>::f` only ever appears applied, we do not need to represent it separate from the arguments and it can all go inside `AliasPred`, i.e.:
```rust
pub enum AliasPred {
trait_id: DefId,
name: Symbol,
generic_args: GenericArgs,
refine_args: RefineArgs,
}
pub enum TyKind {
...
Constr(Pred, Ty),
...
}
pub enum Pred {
Expr(Expr),
Alias(AliasPred)
}
```
Finally, I think we will need a type like:
```rust
pub enum AssocPred {
name: Symbol,
/// The instantiated predicate. |x| 0 < x in the example
pred: Binder<Expr>
// ... maybe more stuff
}
// Something tricky about this definition which I don't see clearly right now
// is that the `Binder<Expr>` is under the `Generics` of the trait, i.e., the
// sorts in the `Binder` may refer to these generics.
```
and a query `assoc_predicates: DefId -> List<AssocPred>` that given the `DefId` of an `impl` block returns the list of associated predicates for that instance. This is analogous to `AssocItem` with the difference that we store the instantiated predicate directly in it instead of requiring an additional query.
## Refining Iterators
```rust=
#[flux::refined_by(is_some: bool)]
enum Option<T>;
#[flux::assoc(fn step(x: Self, y: Self) -> bool]
#[flux::assoc(fn finished(x: Self) -> bool]
trait Iterator {
type Item;
#[flux::sig(
fn(self: &strg Self[@start]) -> Option<Self::Item>{v: Self::finished(start) => !v.is_some}
ensures self: Self{end: Self::step(start, end)}
)]
fn next(&mut self) -> Option<Self::Item>;
}
```
## enumerate using Iterator?
```rust
#[flux::refined_by(idx: int, inner: I)]
struct Enumerate<I>;
#[flux::assoc(
fn step(x: Self, y: Self) -> bool {
x.idx + 1 == y.idx && I::step(x.inner, y.inner)
}]
#[flux::assoc(
fn finished(x: Self) -> bool {
I::finished(x.inner)
}]
impl<I> Iterator for Enumerate<I>
where I: Iterator
{
type Item = (usize, I::Item);
#[flux::sig(
fn(self: &strg Self[@start]) -> Option<(usize[start.idx], I::Item)>{v: Self::finished(start) => !v.is_some}
ensures self: Self{end: Self::step(start, end)}
)]
fn next(&mut self) -> Option<Self::Item>;
}
```
## Wanted: DEFAULT associated refinement implementations
How about we just provide the default in the trait definition, e.g.
```rust=
#[flux::assoc(fn f(x: int) -> int { x + 1 })]
trait MyTrait {}
struct Add1;
// Can skip the next line
// #[flux::assoc(fn f(x: int) -> int { x + 1 })]
impl MyTrait for Add1 {}
struct Add2;
#[flux::assoc(fn f(x: int) -> int { x + 2 })]
impl MyTrait for Add2 {}
#[flux::sig(fn() -> i32{v: v == <Add1 as MyTrait>::f(0)})]
fn test1() -> i32 { 1 }
#[flux::sig(fn() -> i32{v: v == <Add2 as MyTrait>::f(0)})]
fn test2() -> i32 { 2 }
```
```rust
#[flux::refined_by(idx: int, fin: int)]
struct RangeIterator {
idx: i32[idx],
fin: i32[fin],
}
impl RangeIterator {
fn new(idx: i32, fin: i32) -> Self[idx, fin] {
RangeIterator { idx, fin }
}
}
#[flux::assoc(fn step(now:Self, next:Self) -> bool { true })]
#[flux::assoc(fn done(now:Self) -> bool { true })]
trait Iterator {
fn next(self: &strg Self[@now]) -> Option<Self::Item>[#b]
ensures self: Self{next: b => step(now, next)}, b || done(now)
// variant
fn next(self: &strg Self[@now]) -> Option<Self::Item>[!done(now)]
ensures self: Self{next: step(now, next)}, b || done(now)
}
impl Iterator for RangeIterator {
reft fn step(now: RangeIterator, next: RangeIterator) -> bool {
next.idx == now.idx + 1 && next.fin == now.fin
}
reft fn done(now: RangeIterator) -> bool {
now.idx == now.fin
}
type Item = i32;
fn next(self: &strg Self[@now]) -> Option<i32>[#b]
ensures self: Self{next: b => (next.idx == now.idx + 1 && next.fin == now.fin)},
b || now.idx == now.fin
{
let idx = self.idx;
let fin = self.fin;
if idx == fin {
None
} else {
self.idx = idx + 1;
Some(idx)
}
// if idx < fin {
// self.idx = idx + 1;
// Some(idx)
// } else {
// // !(idx < fin)
// // idx == fin
// None
// }
}
}
fn test(){
let mut ctr = 0;
let rng = RangeIterator(1,5);
// rng : { idx = 1, step = |(now, next)| { next.idx = now.idx + 1 && now.fin == now.fin }, done = |now| { now.idx == now.fin} }
while let Some(_) = rng.next() {
// inv(ctr = rng.idx)
ctr += 1;
}
// need rng.idx = 5 when we break out.
assert(ctr == 5)
}
```
## The trouble with default trait specifications
```rust
trait Bob {
fn foo(i32) -> i32
}
struct Evan {}
impl Bob for Evan {
fn foo(x:Pos) -> Pos {
10 / x
}
}
fn mumble<T:Bob>(x:T) {
<T as Bob>::foo(0)
}
fn mumble_with_evan() {
mumble(Evan{})
}
/*
(Nat) -> Nat <: (i32) -> i32
post1 <: post2
pre2 <: pre1
----------------------------------------------
(pre1) -> post1 <: (pre2) -> post2
*/
```
## Substitution Etc.
```rust!
#![allow(dead_code)]
#[flux::sig(fn (f: fn(nat) -> nat) -> nat {
fn foo(f: fn(i32) -> i32) -> i32 {
f(10)
}
#[flux::sig(fn (x:i32) -> i32[x+1])]
fn inc(x: i32) -> i32 {
x + 1
// if x <= 100 {
// x
// } else {
// 100
// }
}
fn baz() -> i32 {
let ptr = CAST(inc) // CAST: FnDef
foo(ptr) // CALL: inc_sig_as_ptr <: nat -> nat
}
```
```rust
// CURRENT Representation of `inc`
inc_sig := EarlyBinder(x: int, Binder(fn(int[x]) -> i32[x+1]))
inc_sig_as_ptr := EarlyBinder([], Binder(x:int, fn(int[x]) -> i32[x+1]))
```
**Option 1:**
- Just do CHECK (against unrefined sig of cast-target) as CAST site
**Option 2:**
- CAST: Convert `inc_sig` -> `inc_sig_as_ptr` (if possible, i.e. no predicates etc.)
- CALL: do the CHECK
- If `x` is mentioned in a predicate ...