Search code examples
c#-4.0code-contracts

Custom Methods in Contract.Ensures


I'm trying to understand Code Contracts in a bit more detail. I've got the following contrived example, where I'm trying to assert the invariant of a try/get pattern that if it returns true then the out object is non-null, otherwise if it returns false.

    public static bool TryParseFruit(string maybeFruit, out Fruit fruit)
    {
        Contract.Requires(maybeFruit != null);

        Contract.Ensures((Contract.Result<bool>() && Contract.ValueAtReturn(out fruit) != null) ||
                         (!Contract.Result<bool>() && Contract.ValueAtReturn(out fruit) == null));

        // Elided for brevity
        if (ICanParseIt())
        {
            fruit = SomeNonNullValue;
            return true;
        }
        else
        {
            fruit = null;
            return false;
        }
    }

I don't like the duplication inside Contract.Ensures so I wanted to factor out my own method for this.

[Pure]
public static bool Implies(bool a, bool b)
{
   return (a && b) || (!a && !b);
}

Then I changed my invariant in TryParseFruit to

Contract.Ensures(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null);

But this generates warnings that the "ensures is unproven". If I then perform the inline refactoring on my Implies method then everything is OK again.

Could someone explain to me why this happens? I'm guessing it's because Contract.ValueAtReturn is used magically in some way that means I can't just pass it's result off to another function and expect it to work.

(Update #1)

I think that all of the following Contract.Ensures express the same thing (namely that if the function returns true then fruit is non-null, otherwise fruit is null). Note that I am only using one of these at a time :)

Contract.Ensures(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null));           
Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn(out fruit) != null));
Contract.Ensures(Contract.Result<bool>() ^ (Contract.ValueAtReturn(out fruit) == null));

However, none of the above Contract.Ensures lead to a clean compile of the code below. I want the Code.Contracts to express that fruit.Name cannot be a null reference.

    Fruit fruit;
    while (!TryGetExample.TryParseFruit(ReadLine(), out fruit))
    {
        Console.WriteLine("Try again...");
    }

    Console.WriteLine(fruit.Name);

I am only able to get the completely clean compile with code contracts if I use the long winded way of expressing this detailed above. My question is why!


Solution

  • Of course, you can also try Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn(out fruit) != null)); I have a vague recollection that the analyzer prefers == to other operators.

    I've had some success tracing these things with Contract.Assert which helps you find out where the hole in the analysis lies. I have also found cases where Contract.Assert allows the analysis to succeed. In other words, you might fix this with a couple of assertions:

    public static bool TryParseFruit(string maybeFruit, out Fruit fruit) 
    { 
        Contract.Requires(maybeFruit != null); 
    
        Contract.Ensures(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null);
    
        // Elided for brevity 
        if (ICanParseIt()) 
        { 
            fruit = SomeNonNullValue; 
            Contract.Assert(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null);
            return true; 
        } 
        else 
        { 
            fruit = null; 
            Contract.Assert(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null);
            return false; 
        } 
    } 
    

    Messy, I know. On the other hand, if any assertion fails, you can look, for example, at other aspects of the logic. For example, you can litter your code with Contract.Assert(SomeNonNullValue != null); to see where the analyzer is losing its certainty about the non-nullness of SomeNonNullValue.

    EDIT

    If the Assert is unproven, but you know that it should be provable, then you can use that to help isolate the problem. I suspect that the problem (or at least part of it) is your lack of Ensures in the Implies method. Try adding Contract.Ensures(Contract.Result<bool>() == (Contract.OldValue(a) == Contract.OldValue(b))); Also, as I again have vague recollections about different handling for different logical operators, try restating the return of that method. For example: return a == b;