// RUN: %testDafnyForEachCompiler "%s" -- --relax-definite-assignment

datatype Result<T> = Success(value: T) | Failure(error: string)
{
  predicate IsFailure() {
    Failure?
  }
  function PropagateFailure<U>(): Result<U>
    requires Failure?
  {
    Failure(this.error)
  }
}
class A {
  method mn() returns (r: Result<int>, out: int)
    ensures out == -2 && r.Failure?
  {
    var o :- m(1);
    assert o == 3;
    print "OK\n";
    out :- this.m(-1); // Should exit with failure
    return Success(0), 4;
  }

  method mn1() returns (r: Result<int>)
    ensures r.Failure?
  {
    :- m1(1);
    print "OK\n";
    :- m1(-1); // SHould exit with failure
    return Success(0);
  }

  method m(i: int) returns (r: Result<int>, o: int)
    ensures 0 <= i ==> r.Success? && r.value == i && o == i+i+i
    ensures i < 0 ==> r.Failure? && o == i+i
  {
    if i < 0 { return Failure("negative"), i+i; }
    return Success(i), i+i+i;
  }

  method m1(i: int) returns (r: Result<int>)
    ensures 0 <= i ==> r.Success? && r.value == i
    ensures i < 0 ==> r.Failure?
  {
    if i < 0 { return Failure("negative"); }
    return Success(i);
  }

  method mexp() returns (r: Result<int>, k: int)
    ensures r.IsFailure() && k == 100
  {
    k :- Result<int>.Failure("always"), 100;
    k := 101; // Not executed
    return Success(0), k;
  }
}

method Main() {
  var a: A := new A;
  var x := a.mn1();
  print x.Failure?, " ";
  var out;
  x, out := a.mn();
  print x.Failure?, " ", out, " ";
  print "End\n";
}

