Skip to content

Commit eb846e8

Browse files
committed
doc/manual: Add contract manual
Signed-off-by: Florian Deljarry <deljarry.florian@gmail.com>
1 parent 3c92d1e commit eb846e8

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed

doc/manual/contract.md

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Contract Programming
2+
3+
Nit supports contract programming like Eiffel. Contracts can be seen as the materialization of specifications in the code itself. The idea is to define the behavior of a property using conditions which will be evaluated on runtime.
4+
5+
In Nit all the conditions of contract must be expressed using boolean expression in the same way as an assertion. Note that the potential routines that compose the condition of the contract should have no side effects.
6+
7+
## Method/Attribute contract
8+
9+
Methods and attributes (setter) can be associated with two types of contracts, entry contracts (expect) and exit contracts (ensure). Together, they define the rules to be respected between the callee and the caller. If the caller fulfills the `expect` condition on entry then the caller can assume that the callee fulfills the `ensure` condition on exit.
10+
11+
Both are declared, between the definition of the signature followed by the keyword `is` and the block start keyword `do`.
12+
13+
### Expect
14+
15+
We define a precondition using the annotation `expect`. Here is an example with a `pop` method which removes the last element of the stack. It defines as a precondition to its call that the stack must not be empty.
16+
17+
~~~
18+
fun pop
19+
is
20+
expect(not_empty)
21+
do
22+
# Remove the last item on the stack
23+
end
24+
~~~
25+
26+
### Ensure
27+
28+
We define a postcondition using the annotation `ensure`. Here is an example with a `push` method which adds the element received as an argument (`object`) at the top of the stack. It garantees to the caller that the object will be added at the top of the stack `self [size] == object`.
29+
30+
~~~
31+
fun push(object: T)
32+
is
33+
ensure(self[size] == object)
34+
do
35+
# Add the given argument `object` on top of the stack
36+
end
37+
~~~
38+
39+
The `ensure` contract offers the possibility of referring to the return value of the method, using the keyword `result`. Example the `get_at` method which returns the object which is stored at the index received as an argument (`index`). It guarantees the caller that the returned element represented by `result` corresponds to the element stored in the stack with the given index `result == self [index]`.
40+
41+
~~~
42+
fun get_at(index: Int): T
43+
is
44+
ensure(result == self[index])
45+
do
46+
# Return the element at the given index.
47+
end
48+
~~~
49+
50+
In `ensure` we can refer to any arguments and instance variables before the method call with the using of `old` keyword. Again, using the pop example, we can ensure that the `size` of the stack after the method call will be the same as before minus one `old(size) - 1 == size`.
51+
52+
~~~
53+
fun pop(object: T)
54+
is
55+
ensure(old(size) - 1 == size)
56+
do
57+
# Remove the last item on the stack
58+
end
59+
~~~
60+
61+
### Additional informations
62+
63+
* When defining a `ensure` or `expect` contract on an attribute, only the automatically generated write property will use the contract.
64+
65+
~~~
66+
var baz: Int is expect(baz > 0) # In the contract, baz refers to the parameter of the write property (setter).
67+
~~~
68+
69+
* The `ensure` or `expect` contracts are evaluated in the same context as the called method, it's possible to refer to attributes, methods and parameters in the condition.
70+
71+
* In `ensure` contracts, the keyword `result` can only be used when the method has a return parameter.
72+
73+
* The expression contained in an `old` is evaluated in the same context as the called method. Thus, it's possible to refer to attributes, methods, and arguments.
74+
75+
* `old(object)` stores a reference to object. By doing so, if `object` is mutated in the method the `old (object)` will also be mutated. To avoid this it's necessary to provide a way to duplicate the object like `clone` method so that `not old (object.clone).is_same_instance (object)` will be true. In the stack example the expresion `old(stack).is_same_instance(stack) && old(stack.size) != old(stack).size` is true. If you need to `old(stack).is_same_instance(stack)` to be false, you need to provide a method to duplicate the object, such as `not old(stack.clone).is_same_instance(stack)` be true.
76+
77+
* Contracts can be declared in several ways, all are equivalent:
78+
79+
~~~
80+
fun foo(a: Int, b: Int)
81+
is
82+
ensure(a > 0, b < 0)
83+
do
84+
# Do something
85+
end
86+
87+
88+
fun foo
89+
is
90+
ensure(a > 0 and b < 0)
91+
do
92+
# Do something
93+
end
94+
95+
96+
fun foo
97+
is
98+
ensure(a > 0)
99+
ensure(b < 0)
100+
do
101+
# Do something
102+
end
103+
~~~
104+
105+
106+
## Class/interface
107+
108+
### Invariant
109+
110+
Invariants are used to specify the characteristics of a class/interface which must always be true, except when executing a member function. In other words, invariants define conditions that must be respected by our instance. Any instance which does not respect one or more of those conditions will be considered as incoherent. Finally, invariants must hold before and after any method called (except those called by `self` directly).
111+
112+
To define an invariant we use the class annotation `invariant`. Here is an example with our Stack. The class defines that it is impossible to be in a state where the size is less than 0.
113+
114+
~~~
115+
class Stack
116+
invariant(size >= 0)
117+
end
118+
~~~
119+
120+
Another example with a class that represents a date (day, month). To be in a consistent state, the date must be represented with a day between 1 and 31 and a month between 1 and 12.
121+
122+
~~~
123+
class Date
124+
invariant(day >= 1 and day <= 31)
125+
invariant(month >= 1 and month <= 12)
126+
end
127+
~~~
128+
129+
### Additional informations
130+
131+
* The invariant is only checked at the end of a constructor.
132+
133+
* By default, the invariants are only checked on exit. See section `Verification policy` to activate them on in and out.
134+
135+
## Inheritance
136+
137+
The Nit contracts fully support inheritance (single or multiple) and refinement. The specialization can be interpreted as subcontracting. Subcontracting element must keep the set of promises initially defined by all previous contractors. Here are the subtyping rules for each type of the contracts:
138+
139+
* The `expect` can be weakened, which guarantees that all the contracts introduced in the previous definitions will always be considered as valid entries. During the specialization of a method it will be possible to redefine its precondition in order the widen the allowed input.
140+
141+
* The `ensure` can be reinforced, which guarantees that all of the expected results introduced in the previous definitions will be respected. During the specialization of a method it will be possible to redefine its postcondition in order to restrict the possible outputs.
142+
143+
* The `invariant` can be reinforced, which guarantees that constraints of validated state previously defined in the superclasses/interfaces will be respected. The specialization of a class makes it possible to restrict all of the valid states. The objective is that an object must always be considered as a coherent value of all of its superclasses/interfaces.
144+
145+
Nit however offers the possibility of removing the inheritance of contracts with the `no contract` annotation (use this annotation only when it's really necessary).
146+
147+
## Runtime
148+
149+
When performing the evaluation of a contract, all routines and elements will be executed without the evaluation of contracts. The objective is to remove the potential risk of falling into an infinite loop of verification.
150+
151+
### Execution order
152+
153+
For the public methods/attribute, the evaluation order is as follows:
154+
155+
1- invariant
156+
2- preconditions (`expect`)
157+
3- called property
158+
4- postconditions (`ensure`)
159+
5- invariant
160+
161+
For constructors only the first step is ignored.
162+
163+
### Verification policy
164+
165+
Since contracts are expensive in resource, Nit offers several levels of contract verification :
166+
167+
Default: In default mode the contracts can be defined as "semi-global". I.E. All contracts (ensure, expect, invariant) used in the main modules are enabled, expects contracts are enabled (ensure, invariant contracts are disable) in direct imported modules. Other indirected imported modules doesn't have active contract.
168+
169+
Full contract: Enable contracts on all modules(`--full-contract` option). Warning: this is an expensive option at runtime.
170+
171+
No contract: all contracts are disabled (option --no-contract).
172+
173+
In out invariant: As indicated previously, invariants are enabled only in exit. It is, however, possible to activate them on entry and exit with the `--in-out-invariant` option.
174+
175+
No self contract: Disables all contracts on member routines of the same instance.

0 commit comments

Comments
 (0)