Method Calls and Returns

A method contract has two sides:

  • Parameters: callers must establish each parameter’s refinement. The method body can assume those refinements.
  • Return value: each return must satisfy the method’s declared refinement. Callers can use that refinement for the result.
import liquidjava.specification.Refinement;

public class MethodExample {
    @Refinement("_ == value")
    public static int positiveIdentity(
            @Refinement("value > 0") int value) {
        return value;
    }

    public static void example() {
        @Refinement("_ == 3") int result = positiveIdentity(3);
        positiveIdentity(0); // Refinement Error: 0 is not positive
    }
}

For positiveIdentity(3), the verifier checks 3 > 0. It then substitutes the argument into the return contract _ == value, so the result is known to equal 3. The call with 0 fails the parameter check.

The body is checked separately using its parameter contract. Returning a value that contradicts its return contract also produces an error:

import liquidjava.specification.Refinement;

public class ReturnExample {
    @Refinement("_ > 0")
    public static int positive() {
        return 0; // Refinement Error: 0 is not positive
    }
}

Write the return properties callers need explicitly. For example, without a return refinement on positiveIdentity, callers should not rely on the verifier inspecting its body to discover that the result equals the argument.

Object State at a Call

For a method with state refinements, the verifier also checks that the receiver satisfies from before the call and uses to to describe its state afterward. A constructor’s to establishes the initial state. This is how a protocol can reject a read() call after close().

For library methods whose source is outside the checked code, use external refinements to supply contracts. Those contracts describe the library behavior the verifier relies on; they do not verify the library’s implementation.