dafny icon indicating copy to clipboard operation
dafny copied to clipboard

`fresh` doesn't imply inequality with `--general-traits=datatype`

Open dschoepe opened this issue 9 months ago • 4 comments

Dafny version

4.6.0

Code to produce this issue

trait Clone {
  method clone() returns (res: Clone)
}

// using this version of `Clone` makes the verification pass:
// trait Clone extends object {
//   method clone() returns (res: Clone)
//     ensures fresh(res)
// }

class A extends Clone {
  method {:axiom} clone() returns (res: Clone)
    ensures res is A
    ensures fresh(res as A)
}

method client(aIn: A, bIn: A)
  requires aIn != bIn
{
  var a := aIn;
  var b := bIn;
  label L1:
  var clone := b.clone();
  assert fresh@L1(clone as A); // verifies
  b := clone as A;
  label L2:
  var clone2 := b.clone();
  assert fresh@L2(clone2 as A); // verifies
  a := clone2 as A;
  assert a != b; // doesn't verify
}

Command to run and resulting output

dafny verify --general-traits=datatype --type-system-refresh

What happened?

The above example verifies both when using the uncommented version of Clone and when running dafny verify --type-system-refresh without --general-traits=datatype. Since we know that clone is A and both clone as A objects are fresh, the example above should verify, also with --general-traits=datatype.

What type of operating system are you experiencing the problem on?

Linux, Mac

dschoepe avatar Apr 29 '24 19:04 dschoepe

I can confirm that --type-system-refresh has an effect on the verification result. What is your take on this @RustanLeino?

stefan-aws avatar Apr 30 '24 13:04 stefan-aws

If you change trait Clone { into trait Clone extends object { then it verifies. There's no need for ensures fresh(res) on the clone method in trait Clone

keyboardDrummer avatar May 01 '24 09:05 keyboardDrummer

The problem with extends object is that it can then no longer be implemented by datatypes. We're using this to model Rust's Clone trait which is implemented by both value types and pointer types (e.g. Box), so we'd like to use the same Clone trait for both.

More broadly, I think I don't understand why the fresh assertions about clone as A don't also imply that the values are not equal. Are you sure this is intended to fail during verification?

dschoepe avatar May 01 '24 22:05 dschoepe

One way you can encode the Clone method of Rust is to actually include the "Self" type as a type parameter for clone. Hence, the following verifies

trait Clone<T> {
  method clone() returns (res: T)
}

class A extends Clone<A> {
  method {:axiom} clone() returns (res: A)
    ensures fresh(res)
}

method client(aIn: A, bIn: A)
  requires aIn != bIn
{
  var a := aIn;
  var b := bIn;
  label L1:
  var clone := b.clone();
  assert fresh@L1(clone as A); // verifies
  b := clone as A;
  label L2:
  var clone2 := b.clone();
  assert fresh@L2(clone2 as A); // verifies
  a := clone2 as A;
  assert a != b; // doesn't verify
}

MikaelMayer avatar May 02 '24 22:05 MikaelMayer