I have downloaded and installed Spec#, but I cannot make any sense of it. There is just not enough online documentation, and the language seems complex enough and different enough from most mainstream languages to make a good deal of documentation necessary.
Anyway I'm sure my Real/Positive example would work as expected in Spec# (i.e., like Eiffel). In fact I've been reading a little bit about DbC in Eiffel and I think I understand what goes on. A class
invariant is simply a postcondition that is added automatically to all methods. In derived classes, it is right to strengthen a postcondition, but it is not right to weaken it. In our class, adding the "value>=0"
invariant to the derived class means that the
invariant (postcondition) is being strengthen, which is as I said perfectly sound. Therefore Cobra should allow this and see that said
invariant is enforced (that's way the behaviour I reported initially was indeed a bug).
On the other hand, a derived class must always have as
invariant the
invariant of its base class. Otherwise, the
invariant (postcondition) of the base class could be easily weakened, which as I said is forbidden. This means Cobra must ensure that the
invariant of a derived class is combined, or added, to the
invariant of its base class.
From what I've said, I think it is clear how invariants should be implemented in Cobra (I'm sure that's how they're implemented in Eiffel too). The following code:
class A
var _x = 0
invariant
_x >= 0
class B inherits A
var _y = 10
invariant
_y >= 10
_x < 100
should be translated to:
class A
var _x = 0
def invariant_impl as bool
return _x >= 0
invariant # will be checked at the end of each method
.invariant_impl
class B inherits A
var _y = 10
def invariant_impl as bool is override
if not base.invariant_impl
return false
return _y >= 10 and _
_x < 100
When things are implemented this way, it is easy for a derived class to strengthen the
invariant of its base class (see how B adds the condition x < 100), which is perfectly fine. In addition, thanks to the implicit call to the base class version of "invariant_impl", it is impossible to weaken the
invariant of the base class, which would be dangerous.
Chuck: I'm sure this is all obvious to you. Also, you've probably already implemented things this way (you said you were already fixing the problem reported in my initial post). However, I felt I had to post my conclusions here, as they might be useful for people that visit this thread in the future.
I'm still concerned about the mix of non-Cobra classes and invariants. Maybe a Cobra class that inherits a non-Cobra class should not be allowed to use any base class fields in its
invariant, as that would lead to things not working as expected. Consider for example:
class C inherits List<of int>
invariant .count == 0
def main is shared
c = C()
c.add(1) # breaks the invariant but nothing happens, because the base class is not "DbC aware"
What I propose is that the compiler detects this problematic situation, and rejects the code with a message along the lines of "Error: inherited member .count cannot be used in the
invariant of derived class C, because base class List<of int> is not a pure Cobra class".