Merge: Contract implementation
authorJean Privat <jean@pryen.org>
Fri, 27 Sep 2019 12:54:46 +0000 (08:54 -0400)
committerJean Privat <jean@pryen.org>
Fri, 27 Sep 2019 12:54:46 +0000 (08:54 -0400)
# Contract.

This pr is a copy of #2747 with some modifications. It has a dependency with the #2779 and #2788.

Adding programming by contract (Design by contract) in Nit language. Contracts works with nit annotations. I decided to cut the contract pr in a smaller part to facilitate reviewing (some feature are not available on it, see section futur feature).

## Annotations

To define a new contract you need to use the corresponding annotation. For example it's possible to define a contract that x parameter must be strictly greater than 5. To do it would be necessary to define the contract in the following way `expects(x > 5)`. All expressions returning a boolean (comparison...) can be used as a condition.

Three annotations were added:

- `expects` to indicate the conditions need to the execution of the methods
- `ensures` to indicate the conditions of guarantee at the end of the execution of the methods
- `no_contract` to remove the inherited contract

This syntaxe is choose because in the next version of c++ (20) (see [P1369](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1369r0.pdf) for a overview) the contract will be define with the keywords expects and ensures. So I think it would be interesting to keep the same principle. But if all nit developper prefer to use `require` and `ensure` it's fine to change it.

## Method contract (ensures, expects)

For each method it's possible to define preconditions (`expects`) and  post-conditions (`ensures`). If the call of the method satisfies the prerequisites (`expects`) of the method, the caller may assume that the return conditions (`ensures`) will be satisfied.

The method contracts can access all the parameters of the method as well as the set of attributes/methods visible in the context of the method. i.e the set of parameters and the set of methods and attributes of the current class can be used (attributes declare locally in the method can not be used). For post-conditions (ensures) the `result` attribute has been added to perform a check on the return value of the method. Currently it's not possible to refer an old value in a ensures (exemple old(length) == length + 1).

## Process

### Building contract phase

A phase is executed to check all the methods. This check is done to know if:

- The method is annotated (redefined or not)

- The method is a redefinition of a method already having a contract (i.e a method that does not add any new conditions to the existing contract).

When a contract is detected the code is `extended` to add the verification features. Two methods are created. The first method is the contract face. Its goal is to provide the call of the contract verification and the call of the original method. With this strategy, the original code of the method is preserved. The second created method is for the contract verification.

#### Exemple

##### Expect:
```nit
class MyClass
fun foo(x: Int)
is
expects(x > 0)
do
[...]
end
end
```
Representation of the compiled class
```nit
class MyClass
fun foo(x: Int)
is
expects(x > 0)
do
[...]
end

fun _contract_foo(x: Int)
do
_expects_foo(x)
foo(x)
end

fun _expects_foo(x: Int)
do
assert x > 0
end
end
```
##### Ensure:
```nit
class MyClass
fun foo(x: Int): Bool
is
ensures(result == true)
do
[...]
return true
end
end
```
Representation of the compiled class

```nit
class MyClass
fun foo(x: Int): Bool
is
ensures(result == true)
do
[...]
return result
end

fun _contract_foo(x: Intl): Bool
do
var result = foo(x)
_ensures_foo(x, result)
return result
end

fun _ensures_foo(x: Int, result: Bool)
do
assert result == true
end
end
```

## Inheritance

Contracts support redefinition and adding condition. Note that when a contract is define in a parent class, it's no longer possible to remove this contract on all the classes that inherit or redefine them. They only need to be increased according to different subtyping rules.

All preconditions (expects) can be weakened. i.e it's possible to provide a new alternative to validate the contract. This corresponds to the use of a logical OR between the old and the new conditions.

All post-conditions (ensure) can be consolidate. i.e the new condition of the contract will provide a new guarantee to the user of the method. This rule can be translates into a logical AND between the old and the new conditions.

### Exemple

#### Expect

```nit
class SubMyClass
super MyClass

redef fun foo(x: Int)
is
expects(x > 0, x == 0)
do
[...]
end

redef fun _expects_foo(x: Int)
do
if x == 0 then return
super(x)
end

redef fun _contract_foo(x: Int)
do
_expects_foo
super(x)
end

end
```

#### Ensure
```nit
class SubMyClass
super MyClass

redef fun foo(x: Int): Bool
is
ensures(result == true, x > 0)
do
[...]
end

redef fun _ensures_foo(x: Int, result: Bool)
do
super
assert super(x, result)
end

redef fun _contract_foo(x: Int, result: Bool): Bool
do
var result = super
_ensures_foo(x, result)
return result
end
end
```

Summary

| Annotation    |  Inheritance condition type  |
| ------------- | -------------|
| expects       |        and  |
| ensures       |        or  |

## Invocation

All calls to contract methods are intercepted to call the contract version. For the moment the contracts are systematically called, whether it's an internal or external call. Only calls to super do not execute the contracts.

This part is subject to evolution with a parameter to activate or not the internal call verification.

## Building

Since contracts are expensive in resource, several levels of use have been defined based on the needed verification:

- No contract: all contracts are disable (option `--no-contract`).

- Default: By  default  the  contracts  can  be  defined as "semi-global". I.E. All contracts (ensures, expects)used in the main package are enabled, the expects contracts are enabled (ensures contracts are disable) in direct imported package. Other indirected imported package doesn't have active contract.

- Full contract: Contracts are enable (ensures, expects) on all classes (option `--full-contract`).

## Future work

This pr has been simplified to facilitate the reviewing.

Future features:

- Support class invariant contracts
- Ability to put contracts on auto-getters and setter
- Detection of query or command to avoid the contract to change the object state (this functionality will probably be an advice because it's difficult to define if a method has no side effect.)
- `Old` support for the ensures contract  to refer at the same value during the expects condition

Some pr will follow to use the contacts directly in the lib (collection...).

### Note

Currently the syntax of contracts is strict, only comparisons and method calls returning bouleans can be used. Indeed the arguments passed to the annotation of contract are directly used for assert. A solution would be given a more open syntax for more complex contracts instead of using a method. But this would cause problems in the syntax of annotations. If you have any suggestion don't hesitate. An issue is available to talk about the potential syntax. (#1340)

Pull-Request: #2790
Reviewed-by: Jean Privat <jean@pryen.org>

46 files changed:
share/man/nitc.md
src/contracts.nit [new file with mode: 0644]
src/frontend/check_annotation.nit
src/frontend/code_gen.nit
src/modelize/modelize_property.nit
src/semantize/scope.nit
src/toolcontext.nit
tests/contracts.nit [new file with mode: 0644]
tests/contracts_abstract.nit [new file with mode: 0644]
tests/contracts_add.nit [new file with mode: 0644]
tests/contracts_attributs.nit [new file with mode: 0644]
tests/contracts_constructor.nit [new file with mode: 0644]
tests/contracts_ensures.nit [new file with mode: 0644]
tests/contracts_ensures_1.nit [new file with mode: 0644]
tests/contracts_ensures_2.nit [new file with mode: 0644]
tests/contracts_ensures_3.nit [new file with mode: 0644]
tests/contracts_ensures_4.nit [new file with mode: 0644]
tests/contracts_ensures_sequence.nit [new file with mode: 0644]
tests/contracts_error.nit [new file with mode: 0644]
tests/contracts_expects.nit [new file with mode: 0644]
tests/contracts_expects_1.nit [new file with mode: 0644]
tests/contracts_expects_2.nit [new file with mode: 0644]
tests/contracts_expects_3.nit [new file with mode: 0644]
tests/contracts_inheritance.nit [new file with mode: 0644]
tests/contracts_same_contract.nit [new file with mode: 0644]
tests/sav/contracts.res [new file with mode: 0644]
tests/sav/contracts_abstract.res [new file with mode: 0644]
tests/sav/contracts_add.res [new file with mode: 0644]
tests/sav/contracts_attributs.res [new file with mode: 0644]
tests/sav/contracts_constructor.res [new file with mode: 0644]
tests/sav/contracts_ensures.res [new file with mode: 0644]
tests/sav/contracts_ensures_1.res [new file with mode: 0644]
tests/sav/contracts_ensures_2.res [new file with mode: 0644]
tests/sav/contracts_ensures_3.res [new file with mode: 0644]
tests/sav/contracts_ensures_4.res [new file with mode: 0644]
tests/sav/contracts_ensures_5.res [new file with mode: 0644]
tests/sav/contracts_ensures_sequence.res [new file with mode: 0644]
tests/sav/contracts_error.res [new file with mode: 0644]
tests/sav/contracts_expects.res [new file with mode: 0644]
tests/sav/contracts_expects_1.res [new file with mode: 0644]
tests/sav/contracts_expects_2.res [new file with mode: 0644]
tests/sav/contracts_expects_3.res [new file with mode: 0644]
tests/sav/contracts_inheritance.res [new file with mode: 0644]
tests/sav/contracts_same_contract.res [new file with mode: 0644]
tests/sav/test_toolcontext_args1.res
tests/sav/test_toolcontext_args2.res

index eff6232..64d8c53 100644 (file)
@@ -539,6 +539,15 @@ Force lazy semantic analysis of the source-code.
 Analysis of methods is thus done only when required.
 This option breaks the behavior of most of the tools since errors in methods are undetected until the method is required in some processing.
 
+## Contract
+By default the contracts can be defined as "semi-global". I.E. All contracts (ensures, expects) used in the main package are enabled, the `expects` contracts are enabled (`ensures` contracts are disable) in direct imported package. Other indirected imported package has no active contract.
+
+### `--no-contract`
+Option used to disable the contracts(ensures, expects) usage.
+
+### `--full-contract`
+Option used to enables contracts (ensures, expects) on all classes. Warning this is an expensive option at runtime.
+
 # ENVIRONMENT VARIABLES
 
 ### `NIT_DIR`
diff --git a/src/contracts.nit b/src/contracts.nit
new file mode 100644 (file)
index 0000000..156bb53
--- /dev/null
@@ -0,0 +1,849 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Module to build contract
+# This module provide extension of the modele to add contracts, the building phase and the "re-driving" to call the contract.
+#
+# FIXME Split the module in three parts: extension of the modele, building phase and the "re-driving"
+module contracts
+
+import astbuilder
+import parse_annotations
+import phase
+import semantize
+intrude import modelize_property
+intrude import scope
+intrude import typing
+
+redef class ToolContext
+       # Parses contracts annotations.
+       var contracts_phase: Phase = new ContractsPhase(self, [modelize_property_phase,typing_phase])
+end
+
+private class ContractsPhase
+       super Phase
+
+       # The entry point of the contract phase
+       # In reality, the contract phase is executed on each module
+       # FIXME: Actually all method is checked to add method if any contract is inherited
+       redef fun process_nmodule(nmodule)do
+               # Check if the contracts are disabled
+               if toolcontext.opt_no_contract.value then return
+               nmodule.do_contracts(self.toolcontext)
+       end
+end
+
+redef class AModule
+       # Compile all contracts
+       #
+       # The implementation of the contracts is done in two visits.
+       #
+       # - First step, the visitor analyzes and constructs the contracts
+       #   for each class (invariant) and method (expects, ensures).
+       #
+       # - Second step the visitor analyzes each `ASendExpr` to see
+       #   if the callsite calls a method with a contract. If this is
+       #       the case the callsite is replaced by another callsite to the contract method.
+       fun do_contracts(toolcontext: ToolContext) do
+               #
+               var contract_visitor = new ContractsVisitor(toolcontext, toolcontext.modelbuilder.identified_modules.first, self, new ASTBuilder(mmodule.as(not null)))
+               contract_visitor.enter_visit(self)
+               #
+               var callsite_visitor = new CallSiteVisitor(toolcontext)
+               callsite_visitor.enter_visit(self)
+       end
+end
+
+# This visitor checks the `AMethPropdef` and the `AClassDef` to check if they have a contract annotation or it's a redefinition with a inheritance contract
+private class ContractsVisitor
+       super Visitor
+
+       var toolcontext: ToolContext
+
+       # The main module
+       # It's strore to define if the contract need to be build depending on the selected policy `--no-contract` `--full-contract` or default
+       var mainmodule: MModule
+
+       # Actual visited module
+       var visited_module: AModule
+
+       var ast_builder: ASTBuilder
+
+       # The `ASignature` of the actual build contract
+       var n_signature: ASignature is noinit
+
+       # The `MSignature` of the actual build contract
+       var m_signature: MSignature is noinit
+
+       # The `current_location` can corresponding of the annotation or method location.
+       var current_location: Location is noinit
+
+       # Is the contrat is an introduction or not?
+       var is_intro_contract: Bool is noinit
+
+       # Actual visited class
+       var visited_class: nullable AClassdef
+
+       # is `no_contract` annotation was found
+       var find_no_contract = false
+
+       redef fun visit(node)
+       do
+               node.create_contracts(self)
+               node.visit_all(self)
+       end
+
+       # Method use to define the signature part of the method `ASignature` and `MSignature`
+       # The given `mcontract` provided `adapt_nsignature` and `adapt_msignature` to copy and adapt the given signature (`nsignature` and `msignature`)
+       fun define_signature(mcontract: MContract, nsignature: nullable ASignature, msignature: nullable MSignature)
+       do
+               if nsignature != null and msignature != null then nsignature.ret_type = msignature.return_mtype
+               self.n_signature = mcontract.adapt_nsignature(nsignature)
+               self.m_signature = mcontract.adapt_msignature(msignature)
+       end
+
+       # Define the new contract take in consideration that an old contract exist or not
+       private fun build_contract(n_annotation: AAnnotation, mcontract: MContract, mclassdef: MClassDef): MMethodDef
+       do
+               self.current_location = n_annotation.location
+               # Retrieving the expression provided in the annotation
+               var n_condition = n_annotation.construct_condition(self)
+               var m_contractdef: AMethPropdef
+               if is_intro_contract then
+                       # Create new contract method
+                       m_contractdef = mcontract.create_intro_contract(self, n_condition, mclassdef)
+               else
+                       # Create a redef of contract to take in consideration the new condition
+                       m_contractdef = mcontract.create_subcontract(self, n_condition, mclassdef)
+               end
+               var contract_propdef = m_contractdef.mpropdef
+               # The contract has a null mpropdef, this should never happen
+               assert contract_propdef != null
+               return contract_propdef
+       end
+end
+
+# This visitor checks the `callsite` to see if the target `mpropdef` has a contract.
+private class CallSiteVisitor
+       super Visitor
+
+       var toolcontext: ToolContext
+
+       # Actual visited method
+       var visited_method: APropdef is noinit
+
+       redef fun visit(node)
+       do
+               node.check_callsite(self)
+               node.visit_all(self)
+       end
+
+       # Check if the callsite calls a method with a contract.
+       # If it's the case the callsite is replaced by another callsite to the contract method.
+       private fun drive_method_contract(callsite: CallSite): CallSite
+       do
+               if callsite.mpropdef.has_contract then
+                       var contract_facet = callsite.mproperty.mcontract_facet
+                       var visited_mpropdef = visited_method.mpropdef
+                       assert contract_facet != null and visited_mpropdef != null
+
+                       var unsafe_mtype = callsite.recv.resolve_for(callsite.recv, callsite.anchor, visited_mpropdef.mclassdef.mmodule, false)
+
+                       # This check is needed because the contract can appear after the introduction.
+                       if unsafe_mtype.has_mproperty(visited_method.mpropdef.mclassdef.mmodule, contract_facet) then
+                               var type_visitor = new TypeVisitor(toolcontext.modelbuilder, visited_mpropdef)
+                               var drived_callsite = type_visitor.build_callsite_by_property(visited_method, callsite.recv, contract_facet, callsite.recv_is_self)
+                               # This never happen this check is here for debug
+                               assert drived_callsite != null
+                               return drived_callsite
+                       end
+               end
+               return callsite
+       end
+end
+
+redef class ANode
+       private fun create_contracts(v: ContractsVisitor) do end
+       private fun check_callsite(v: CallSiteVisitor) do end
+end
+
+redef class AAnnotation
+
+       # Returns the conditions of annotation parameters in the form of and expr
+       # exemple:
+       # the contract ensures(true, i == 10, f >= 1.0)
+       # return this condition (true and i == 10 and f >= 1.0)
+       private fun construct_condition(v : ContractsVisitor): AExpr
+       do
+               var n_condition = n_args.first
+               n_args.remove_at(0)
+               for n_arg in n_args do n_condition = v.ast_builder.make_and(n_condition, n_arg)
+               return n_condition
+       end
+end
+
+# The root of all contracts
+#
+# The objective of this class is to provide the set
+# of services must be implemented or provided by a contract
+abstract class MContract
+       super MMethod
+
+       # Define the name of the contract
+       fun contract_name: String is abstract
+
+       # Checking if the use of the contract is necessary
+       private fun check_usage(v: ContractsVisitor, actual_mmodule: MModule): Bool is abstract
+
+       # Method use to diplay warning when the contract is not present at the introduction
+       private fun no_intro_contract(v: ContractsVisitor, a: AAnnotation)do end
+
+       # Creating specific inheritance block contract
+       private fun create_nblock(v: ContractsVisitor, n_condition: AExpr, args: Array[AExpr]): ABlockExpr is abstract
+
+       # Method to adapt the `n_mpropdef.n_block` to the contract
+       private fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef) is abstract
+
+       # Adapt the msignature specifically for the contract method
+       private fun adapt_specific_msignature(m_signature: MSignature): MSignature do return m_signature.adapt_to_condition
+
+       # Adapt the nsignature specifically for the contract method
+       private fun adapt_specific_nsignature(n_signature: ASignature): ASignature do return n_signature.adapt_to_condition(null)
+
+       # Adapt the `m_signature` to the contract
+       # If it is not null call the specific adapt `m_signature` for the contract
+       private fun adapt_msignature(m_signature: nullable MSignature): MSignature
+       do
+               if m_signature == null then return new MSignature(new Array[MParameter])
+               return adapt_specific_msignature(m_signature)
+       end
+
+       # Adapt the `n_signature` to the contract
+       # If it is not null call the specific adapt `n_signature` for the contract
+       private fun adapt_nsignature(n_signature: nullable ASignature): ASignature
+       do
+               if n_signature == null then return new ASignature
+               return adapt_specific_nsignature(n_signature)
+       end
+
+       # Create a new empty contract
+       private fun create_empty_contract(v: ContractsVisitor, mclassdef: MClassDef, msignature: nullable MSignature, n_signature: ASignature)
+       do
+               var n_contract_def = intro_mclassdef.mclass.create_empty_method(v, self, mclassdef, msignature, n_signature)
+               n_contract_def.do_all(v.toolcontext)
+       end
+
+       # Create the initial contract (intro)
+       # All contracts have the same implementation for the introduction.
+       #
+       # fun contrat([...])
+       # do
+       #       assert contract_condition
+       # end
+       #
+       private fun create_intro_contract(v: ContractsVisitor, n_condition: nullable AExpr, mclassdef: MClassDef): AMethPropdef
+       do
+               var n_block = v.ast_builder.make_block
+               if n_condition != null then
+                       # Create a new tid to set the name of the assert for more explicit error
+                       var tid = new TId.init_tk(self.location)
+                       tid.text = "{self.contract_name}"
+                       n_block.add v.ast_builder.make_assert(tid, n_condition, null)
+               end
+               return make_contract(v, n_block, mclassdef)
+       end
+
+       # Create a contract with old (super) and the new conditions
+       private fun create_subcontract(v: ContractsVisitor, ncondition: nullable AExpr, mclassdef: MClassDef): AMethPropdef
+       do
+               var args = v.n_signature.make_parameter_read(v.ast_builder)
+               var n_block = v.ast_builder.make_block
+               if ncondition != null then n_block = self.create_nblock(v, ncondition, args)
+               return make_contract(v, n_block, mclassdef)
+       end
+
+       # Build a method with a specific block `n_block` in a specified `mclassdef`
+       private fun make_contract(v: ContractsVisitor, n_block: AExpr, mclassdef: MClassDef): AMethPropdef
+       do
+               var n_contractdef = intro_mclassdef.mclass.create_empty_method(v, self, mclassdef, v.m_signature, v.n_signature)
+               n_contractdef.n_block = n_block
+               # Define the location of the new method for corresponding of the annotation location
+               n_contractdef.location = v.current_location
+               n_contractdef.do_all(v.toolcontext)
+               return n_contractdef
+       end
+end
+
+# A expect (precondition) contract representation
+# This method check if the requirements of the called method is true.
+class MExpect
+       super MContract
+
+       # Define the name of the contract
+       redef fun contract_name: String do return "expects"
+
+       # Verification if the construction of the contract is necessary.
+       # Three cases are checked for `expects`:
+       #
+       # - Is the `--full-contract` option it's use?
+       # - Is the method is in the main package
+       # - Is the method is in a direct imported package.
+       #
+       redef fun check_usage(v: ContractsVisitor, actual_mmodule: MModule): Bool
+       do
+               var main_package = v.mainmodule.mpackage
+               var actual_package = actual_mmodule.mpackage
+               if main_package != null and actual_package != null then
+                       var condition_direct_arc = v.toolcontext.modelbuilder.model.mpackage_importation_graph.has_arc(main_package, actual_package)
+                       return v.toolcontext.opt_full_contract.value or condition_direct_arc or main_package == actual_package
+               end
+               return false
+       end
+
+       # Display warning if no contract is defined at introduction `expect`,
+       # because if no contract is defined at the introduction the added
+       # contracts will not cause any error even if they are not satisfied.
+       #
+       # exemple
+       # ~~~nitish
+       # class A
+       #       fun bar [...]
+       #       fun _bar_expects([...])
+       #       do
+       #               [empty contract]
+       #       end
+       # end
+       #
+       # redef class A
+       #       redef fun bar is expects(contract_condition)
+       #       redef fun _bar_expects([...])
+       #       do
+       #               if not (contract_condition) then super
+       #       end
+       # end
+       # ~~~~
+       #
+       redef fun no_intro_contract(v: ContractsVisitor, a: AAnnotation)
+       do
+               v.toolcontext.warning(a.location,"","Useless contract: No contract defined at the introduction of the method")
+       end
+
+       redef fun create_nblock(v: ContractsVisitor, n_condition: AExpr, args: Array[AExpr]): ABlockExpr
+       do
+               # Creating the if expression with the new condition
+               var if_block = v.ast_builder.make_if(n_condition, n_condition.mtype)
+               # Creating and adding return expr to the then case
+               if_block.n_then = v.ast_builder.make_return(null)
+               # Creating the super call to the contract and adding this to else case
+               if_block.n_else = v.ast_builder.make_super_call(args,null)
+               var n_block = v.ast_builder.make_block
+               n_block.add if_block
+               return n_block
+       end
+
+       redef fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef)
+       do
+               var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_mpropdef, self, true)
+               var n_self = new ASelfExpr
+               var args = n_mpropdef.n_signature.make_parameter_read(v.ast_builder)
+               var n_callexpect = v.ast_builder.make_call(n_self,callsite,args)
+               # Creation of the new instruction block with the call to expect condition
+               var actual_expr = n_mpropdef.n_block
+               var new_block = new ABlockExpr
+               new_block.n_expr.push n_callexpect
+               if actual_expr isa ABlockExpr then
+                       new_block.n_expr.add_all(actual_expr.n_expr)
+               else if actual_expr != null then
+                       new_block.n_expr.push(actual_expr)
+               end
+               n_mpropdef.n_block = new_block
+       end
+end
+
+# The root of all contracts where the call is after the execution of the original method (`invariants` and `ensures`).
+abstract class BottomMContract
+       super MContract
+
+       # Verification if the construction of the contract is necessary.
+       # Two cases are checked for `expects`:
+       #
+       # - Is the `--full-contract` option it's use?
+       # - Is the method is in the main package
+       #
+       redef fun check_usage(v: ContractsVisitor, actual_mmodule: MModule): Bool
+       do
+               return v.toolcontext.opt_full_contract.value or v.mainmodule.mpackage == actual_mmodule.mpackage
+       end
+
+       redef fun create_nblock(v: ContractsVisitor, n_condition: AExpr, args: Array[AExpr]): ABlockExpr
+       do
+               var tid = new TId.init_tk(v.current_location)
+               tid.text = "{contract_name}"
+               # Creating the assert expression with the new condition
+               var assert_block = v.ast_builder.make_assert(tid,n_condition,null)
+               # Creating the super call to the contract
+               var super_call = v.ast_builder.make_super_call(args,null)
+               # Define contract block
+               var n_block = v.ast_builder.make_block
+               # Adding the expressions to the block
+               n_block.add super_call
+               n_block.add assert_block
+               return n_block
+       end
+
+       # Inject the result variable in the `n_block` of the given `n_mpropdef`.
+       private fun inject_result(v: ContractsVisitor, n_mpropdef: AMethPropdef, ret_type: MType): Variable
+       do
+               var actual_block = n_mpropdef.n_block
+               # never happen. If it's the case the problem is in the contract facet building
+               assert actual_block isa ABlockExpr
+
+               var return_var: nullable Variable = null
+
+               var return_expr = actual_block.n_expr.last.as(AReturnExpr)
+
+               var returned_expr = return_expr.n_expr
+               # The return node has no returned expression
+               assert returned_expr != null
+
+               # Check if the result variable already exit
+               if returned_expr isa AVarExpr then
+                       if returned_expr.variable.name == "result" then
+                               return_var = returned_expr.variable
+                       end
+               end
+
+               return_var = new Variable("result")
+               # Creating a new variable to keep the old return of the method
+               var assign_result = v.ast_builder.make_var_assign(return_var, returned_expr)
+               # Remove the actual return
+               actual_block.n_expr.pop
+               # Set the return type
+               return_var.declared_type = ret_type
+               # Adding the reading expr of result variable to the block
+               actual_block.add assign_result
+               # Expr to read the result variable
+               var read_result = v.ast_builder.make_var_read(return_var, ret_type)
+               # Definition of the new return
+               return_expr = v.ast_builder.make_return(read_result)
+               actual_block.add return_expr
+               return return_var
+       end
+end
+
+# A ensure (postcondition) representation
+# This method check if the called method respects the expectations of the caller.
+class MEnsure
+       super BottomMContract
+
+       # Define the name of the contract
+       redef fun contract_name: String do return "ensures"
+
+       redef fun adapt_specific_msignature(m_signature: MSignature): MSignature
+       do
+               return m_signature.adapt_to_ensurecondition
+       end
+
+       redef fun adapt_specific_nsignature(n_signature: ASignature): ASignature
+       do
+               return n_signature.adapt_to_ensurecondition
+       end
+
+       redef fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef)
+       do
+               var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_mpropdef, self, true)
+               var n_self = new ASelfExpr
+               # argument to call the contract method
+               var args = n_mpropdef.n_signature.make_parameter_read(v.ast_builder)
+               # Inject the variable result
+               # The cast is always safe because the online adapted method is the contract facet
+
+               var actual_block = n_mpropdef.n_block
+               # never happen. If it's the case the problem is in the contract facet building
+               assert actual_block isa ABlockExpr
+
+               var ret_type = n_mpropdef.mpropdef.mproperty.intro.msignature.return_mtype
+               if ret_type != null then
+                       var result_var = inject_result(v, n_mpropdef, ret_type)
+                       # Expr to read the result variable
+                       var read_result = v.ast_builder.make_var_read(result_var, ret_type)
+                       var return_expr = actual_block.n_expr.pop
+                       # Adding the read return to argument
+                       args.add(read_result)
+                       var n_callcontract = v.ast_builder.make_call(n_self,callsite,args)
+                       actual_block.add_all([n_callcontract,return_expr])
+               else
+                       # build the call to the contract method
+                       var n_callcontract = v.ast_builder.make_call(n_self,callsite,args)
+                       actual_block.add n_callcontract
+               end
+       end
+end
+
+redef class MClass
+
+       # This method create an abstract method representation with this MMethodDef an this AMethoddef
+       private fun create_abstract_method(v: ContractsVisitor, mproperty: MMethod, mclassdef: MClassDef, msignature: nullable MSignature, n_signature: ASignature): AMethPropdef
+       do
+               # new methoddef definition
+               var m_def = new MMethodDef(mclassdef, mproperty, v.current_location)
+               # set the signature
+               if msignature != null then m_def.msignature = msignature.clone
+               # set the abstract flag
+               m_def.is_abstract = true
+               # Build the new node method
+               var n_def = v.ast_builder.make_method(null,null,m_def,n_signature,null,null,null,null)
+               # Define the location of the new method for corresponding of the actual method
+               n_def.location = v.current_location
+               # Association new npropdef to mpropdef
+               v.toolcontext.modelbuilder.unsafe_add_mpropdef2npropdef(m_def,n_def)
+               return n_def
+       end
+
+       # Create method with an empty block
+       # the `mproperty` i.e the global property definition. The mclassdef to set where the method is declared and it's model `msignature` and ast `n_signature` signature
+       private fun create_empty_method(v: ContractsVisitor, mproperty: MMethod, mclassdef: MClassDef, msignature: nullable MSignature, n_signature: ASignature): AMethPropdef
+       do
+               var n_def = create_abstract_method(v, mproperty, mclassdef, msignature, n_signature)
+               n_def.mpropdef.is_abstract = false
+               n_def.n_block = v.ast_builder.make_block
+               return n_def
+       end
+end
+
+redef class MMethod
+
+       # The contract facet of the method
+       # it's representing the method with contract
+       # This method call the contract and the method
+       var mcontract_facet: nullable MMethod = null
+
+       # The expected contract method
+       var mexpect: nullable MExpect = null
+
+       # The ensure contract method
+       var mensure: nullable MEnsure = null
+
+       # Check if the MMethod has no ensure contract
+       # if this is the case returns false and built it
+       # else return true
+       private fun check_exist_ensure: Bool
+       do
+               if self.mensure != null then return true
+               # build a new `MEnsure` contract
+               self.mensure = new MEnsure(intro_mclassdef, "_ensures_{name}", intro_mclassdef.location, public_visibility)
+               return false
+       end
+
+       # Check if the MMethod has no expect contract
+       # if this is the case returns false and built it
+       # else return true
+       private fun check_exist_expect: Bool
+       do
+               if self.mexpect != null then return true
+               # build a new `MExpect` contract
+               self.mexpect = new MExpect(intro_mclassdef, "_expects_{name}", intro_mclassdef.location, public_visibility)
+               return false
+       end
+
+       # Check if the MMethod has an contract facet
+       # If the method has no contract facet she create it
+       private fun check_exist_contract_facet(mpropdef : MMethodDef): Bool
+       do
+               if self.mcontract_facet != null then return true
+               # build a new `MMethod` contract
+               self.mcontract_facet = new MMethod(mpropdef.mclassdef, "_contract_{name}", mpropdef.mclassdef.location, public_visibility)
+               return false
+       end
+end
+
+redef class MMethodDef
+
+       # Verification of the contract facet
+       # Check if a contract facet already exists to use it again or if it is necessary to create it.
+       private fun check_contract_facet(v: ContractsVisitor, n_signature: ASignature, mcontract: MContract, exist_contract: Bool)
+       do
+               var exist_contract_facet = mproperty.check_exist_contract_facet(self)
+               if exist_contract_facet and exist_contract then return
+
+               var contract_facet: AMethPropdef
+               if not exist_contract_facet then
+                       # If has no contract facet just create it
+                       contract_facet = create_contract_facet(v, n_signature)
+               else
+                       # Check if the contract facet already exist in this context (in this mclassdef)
+                       if mclassdef.mpropdefs_by_property.has_key(mproperty.mcontract_facet) then
+                               # get the define
+                               contract_facet = v.toolcontext.modelbuilder.mpropdef2node(mclassdef.mpropdefs_by_property[mproperty.mcontract_facet]).as(AMethPropdef)
+                       else
+                               # create a new contract facet definition
+                               contract_facet = create_contract_facet(v, n_signature)
+                               var block = v.ast_builder.make_block
+                               # super call to the contract facet
+                               var n_super_call = v.ast_builder.make_super_call(n_signature.make_parameter_read(v.ast_builder), null)
+                               # verification for add a return or not
+                               if contract_facet.mpropdef.msignature.return_mtype != null then
+                                       block.add(v.ast_builder.make_return(n_super_call))
+                               else
+                                       block.add(n_super_call)
+                               end
+                               contract_facet.n_block = block
+                       end
+               end
+               contract_facet.adapt_block_to_contract(v, mcontract, contract_facet)
+               contract_facet.location = v.current_location
+               contract_facet.do_all(v.toolcontext)
+       end
+
+       # Method to create a contract facet of the method
+       private fun create_contract_facet(v: ContractsVisitor, n_signature: ASignature): AMethPropdef
+       do
+               var contract_facet = mproperty.mcontract_facet
+               assert contract_facet != null
+               # Defines the contract phase is an init or not
+               # it is necessary to use the contracts on constructor
+               contract_facet.is_init = self.mproperty.is_init
+
+               # check if the method has an `msignature` to copy it.
+               var m_signature: nullable MSignature = null
+               if mproperty.intro.msignature != null then m_signature = mproperty.intro.msignature.clone
+
+               var n_contractdef = mclassdef.mclass.create_empty_method(v, contract_facet, mclassdef, m_signature, n_signature)
+               # FIXME set the location because the callsite creation need the node location
+               n_contractdef.location = v.current_location
+               n_contractdef.validate
+
+               var block = v.ast_builder.make_block
+               var n_self = new ASelfExpr
+               var args = n_contractdef.n_signature.make_parameter_read(v.ast_builder)
+               var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_contractdef, mproperty, true)
+               var n_call = v.ast_builder.make_call(n_self, callsite, args)
+
+               if m_signature.return_mtype == null then
+                       block.add(n_call)
+               else
+                       block.add(v.ast_builder.make_return(n_call))
+               end
+
+               n_contractdef.n_block = block
+               n_contractdef.do_all(v.toolcontext)
+               return n_contractdef
+       end
+
+       # Entry point to build contract (define the contract facet and define the contract method verification)
+       private fun construct_contract(v: ContractsVisitor, n_signature: ASignature, n_annotation: AAnnotation, mcontract: MContract, exist_contract: Bool)
+       do
+               if check_same_contract(v, n_annotation, mcontract) then return
+               # Check if the contract is necessary
+               if not mcontract.check_usage(v, mclassdef.mmodule) then return
+               if not exist_contract and not is_intro then no_intro_contract(v, n_signature, mcontract, n_annotation)
+               v.define_signature(mcontract, n_signature, mproperty.intro.msignature)
+
+               var conditiondef = v.build_contract(n_annotation, mcontract, mclassdef)
+               check_contract_facet(v, n_signature.clone, mcontract, exist_contract)
+               has_contract = true
+       end
+
+       # Create a contract on the introduction classdef of the property.
+       # Display an warning message if necessary
+       private fun no_intro_contract(v: ContractsVisitor, n_signature: ASignature, mcontract: MContract, n_annotation: AAnnotation)
+       do
+               mcontract.create_empty_contract(v, mcontract.intro_mclassdef, mcontract.adapt_msignature(self.mproperty.intro.msignature), mcontract.adapt_nsignature(n_signature))
+               mcontract.no_intro_contract(v, n_annotation)
+               mproperty.intro.has_contract = true
+       end
+
+       # Is the contract already defined in the context
+       #
+       # Exemple :
+       # fun foo is expects([...]), expects([...])
+       #
+       # Here `check_same_contract` display an error when the second expects is processed
+       private fun check_same_contract(v: ContractsVisitor, n_annotation: AAnnotation ,mcontract: MContract): Bool
+       do
+               if self.mclassdef.mpropdefs_by_property.has_key(mcontract) then
+                       v.toolcontext.error(n_annotation.location, "The method already has a defined `{mcontract.contract_name}` contract at line {self.mclassdef.mpropdefs_by_property[mcontract].location.line_start}")
+                       return true
+               end
+               return false
+       end
+end
+
+redef class MPropDef
+       # flag to indicate is the `MPropDef` has a contract
+       var has_contract = false
+end
+
+redef class APropdef
+       redef fun check_callsite(v)
+       do
+               v.visited_method = self
+       end
+end
+
+redef class AMethPropdef
+
+       # Execute all method verification scope flow and typing.
+       # It also execute an ast validation to define all parents and all locations
+       private fun do_all(toolcontext: ToolContext)
+       do
+               self.validate
+               # FIXME: The `do_` usage it is maybe to much (verification...). Solution: Cut the `do_` methods into simpler parts
+               self.do_scope(toolcontext)
+               self.do_flow(toolcontext)
+               self.do_typing(toolcontext.modelbuilder)
+       end
+
+       # Entry point to create a contract (verification of inheritance, or new contract).
+       redef fun create_contracts(v)
+       do
+               v.ast_builder.check_mmodule(mpropdef.mclassdef.mmodule)
+
+               v.current_location = self.location
+               v.is_intro_contract = mpropdef.is_intro
+
+               if n_annotations != null then
+                       for n_annotation in n_annotations.n_items do
+                               check_annotation(v,n_annotation)
+                       end
+               end
+
+               if not mpropdef.is_intro and not v.find_no_contract then
+                       self.check_redef(v)
+               end
+
+               # reset the flag
+               v.find_no_contract = false
+       end
+
+       # Verification of the annotation to know if it is a contract annotation.
+       # If this is the case, we built the appropriate contract.
+       private fun check_annotation(v: ContractsVisitor, n_annotation: AAnnotation)
+       do
+               if n_annotation.name == "expects" then
+                       var exist_contract = mpropdef.mproperty.check_exist_expect
+                       mpropdef.construct_contract(v, self.n_signature.as(not null), n_annotation, mpropdef.mproperty.mexpect.as(not null), exist_contract)
+               else if n_annotation.name == "ensures" then
+                       var exist_contract = mpropdef.mproperty.check_exist_ensure
+                       mpropdef.construct_contract(v, self.n_signature.as(not null), n_annotation, mpropdef.mproperty.mensure.as(not null), exist_contract)
+               else if n_annotation.name == "no_contract" then
+                       # no_contract found set the flag to true
+                       v.find_no_contract = true
+               end
+       end
+
+       # Verification if the method have an inherited contract to apply it.
+       private fun check_redef(v: ContractsVisitor)
+       do
+               # Check if the method has an attached contract
+               if not mpropdef.has_contract then
+                       if mpropdef.mproperty.intro.has_contract then
+                               mpropdef.has_contract = true
+                       end
+               end
+       end
+
+       # Adapt the block to use the contracts
+       private fun adapt_block_to_contract(v: ContractsVisitor, contract: MContract, n_mpropdef: AMethPropdef)
+       do
+               contract.adapt_block_to_contract(v, n_mpropdef)
+               mpropdef.has_contract = true
+               n_mpropdef.do_all(v.toolcontext)
+       end
+end
+
+redef class MSignature
+
+       # Adapt signature for a expect condition
+       # Removed the return type is it not necessary
+       private fun adapt_to_condition: MSignature do return new MSignature(mparameters.to_a, null)
+
+       # Adapt signature for a ensure condition
+       #
+       # Create new parameter with the return type
+       private fun adapt_to_ensurecondition: MSignature
+       do
+               var rtype = return_mtype
+               var msignature = adapt_to_condition
+               if rtype != null then
+                       msignature.mparameters.add(new MParameter("result", rtype, false))
+               end
+               return msignature
+       end
+
+       # Adapt signature for a expect condition
+       # Removed the return type is it not necessary
+       private fun clone: MSignature do return new MSignature(mparameters.to_a, return_mtype)
+end
+
+redef class ASignature
+
+       # Create an array of AVarExpr representing the read of every parameters
+       private fun make_parameter_read(ast_builder: ASTBuilder): Array[AVarExpr]
+       do
+               var args = new Array[AVarExpr]
+               for n_param in self.n_params do
+                       var mtype = n_param.variable.declared_type
+                       var variable = n_param.variable
+                       if variable != null and mtype != null then
+                               args.push ast_builder.make_var_read(variable, mtype)
+                       end
+               end
+               return args
+       end
+
+       # Return a copy of self adapted for the expect condition
+       # npropdef it is use to define the parent of the parameters
+       private fun adapt_to_condition(return_type: nullable AType): ASignature
+       do
+               var adapt_nsignature = self.clone
+               adapt_nsignature.n_type = return_type
+               return adapt_nsignature
+       end
+
+       # Return a copy of self adapted for postcondition on npropdef
+       private fun adapt_to_ensurecondition: ASignature do
+               var nsignature = adapt_to_condition(null)
+               if ret_type != null then
+                       var n_id = new TId
+                       n_id.text = "result"
+                       var new_param = new AParam
+                       new_param.n_id = n_id
+                       new_param.variable = new Variable(n_id.text)
+                       new_param.variable.declared_type = ret_type
+                       nsignature.n_params.add new_param
+               end
+               return nsignature
+       end
+end
+
+redef class ASendExpr
+       redef fun check_callsite(v)
+       do
+               var actual_callsite = callsite
+               if actual_callsite != null then
+                       callsite = v.drive_method_contract(actual_callsite)
+               end
+       end
+end
+
+redef class ANewExpr
+       redef fun check_callsite(v)
+       do
+               var actual_callsite = callsite
+               if actual_callsite != null then
+                       callsite = v.drive_method_contract(actual_callsite)
+               end
+       end
+end
index b002d16..41ed278 100644 (file)
@@ -111,6 +111,10 @@ before_all
 after
 after_all
 example
+
+expects
+ensures
+no_contract
 """
 
        # Efficient set build from `primtives_annotations_list`
index 48f1aa7..43b386f 100644 (file)
@@ -19,3 +19,4 @@ import frontend
 import actors_generation_phase
 import serialization_code_gen_phase
 import explain_assert
+import contracts
index c8a7f3b..35e210e 100644 (file)
@@ -42,6 +42,17 @@ redef class ModelBuilder
        # Public clients need to use `mpropdef2node` to access stuff.
        private var mpropdef2npropdef = new HashMap[MPropDef, APropdef]
 
+       # Associate a `npropdef` with its `mpropdef`
+       #
+       # Be careful, this method is unsafe, no checking is done when it's used.
+       # The safe way to add method it's to use the `build_property`
+       #
+       # See `mpropdef2npropdef`
+       fun unsafe_add_mpropdef2npropdef(mpropdef: MPropDef,npropdef: APropdef)
+       do
+               mpropdef2npropdef[mpropdef] = npropdef
+       end
+
        # Retrieve the associated AST node of a mpropertydef.
        # This method is used to associate model entity with syntactic entities.
        #
index 4c5da3f..de85c22 100644 (file)
@@ -268,6 +268,11 @@ redef class AParam
        var variable: nullable Variable
        redef fun accept_scope_visitor(v)
        do
+               if variable != null then
+                       v.register_variable(self.n_id, variable.as(not null))
+                       return
+               end
+
                super
                var nid = self.n_id
                var variable = new Variable(nid.text)
index 0797723..043e9ee 100644 (file)
@@ -405,12 +405,18 @@ class ToolContext
        # Option --stub-man
        var opt_stub_man = new OptionBool("Generate a stub manpage in pandoc markdown format", "--stub-man")
 
+       # Option --no-contract
+       var opt_no_contract = new OptionBool("Disable the contracts usage", "--no-contract")
+
+       # Option --full-contract
+       var opt_full_contract = new OptionBool("Enable all contracts usage", "--full-contract")
+
        # Verbose level
        var verbose_level: Int = 0
 
        init
        do
-               option_context.add_option(opt_warn, opt_warning, opt_quiet, opt_stop_on_first_error, opt_keep_going, opt_no_color, opt_log, opt_log_dir, opt_nit_dir, opt_help, opt_version, opt_set_dummy_tool, opt_verbose, opt_bash_completion, opt_stub_man)
+               option_context.add_option(opt_warn, opt_warning, opt_quiet, opt_stop_on_first_error, opt_keep_going, opt_no_color, opt_log, opt_log_dir, opt_nit_dir, opt_help, opt_version, opt_set_dummy_tool, opt_verbose, opt_bash_completion, opt_stub_man, opt_no_contract, opt_full_contract)
 
                # Hide some internal options
                opt_stub_man.hidden = true
diff --git a/tests/contracts.nit b/tests/contracts.nit
new file mode 100644 (file)
index 0000000..b804288
--- /dev/null
@@ -0,0 +1,41 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test the creation ans the usage on simply contract, in the same time check the no modification of the parameter.
+# See the `foo` method in `Myclass` the change of x in a body method has no effect on the ensure because x is a primitive type and it's given by copy and not by reference.
+
+class MyClass
+       fun foo(x: Int)
+       is
+               expects(x == 1)
+               ensures(x > 0)
+       do
+               x = 0
+       end
+end
+
+class MyClass2
+       fun foo(bool: Bool)
+       is
+               expects(not bool)
+               ensures(not bool)
+       do
+               if bool then print "Error"
+       end
+end
+
+var first = new MyClass
+first.foo(1)
+var second = new MyClass2
+second.foo(true) #Fail
diff --git a/tests/contracts_abstract.nit b/tests/contracts_abstract.nit
new file mode 100644 (file)
index 0000000..de62490
--- /dev/null
@@ -0,0 +1,55 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Verification if it's possible to define a contract in different context (abstract class, interface and class) and it's possible to inherit it.
+
+class MyClass
+       fun foo(x: Int, y: Float)is abstract, expects(x != 10)
+end
+
+abstract class AbstractClass
+       fun bar(x: Int, y: Float)is abstract, expects(x >= 1), ensures(y == 10.0)
+end
+
+interface Interface
+       fun baz(x: Int, y: Float)is abstract, ensures(y <= 10.0, y == 42.0)
+end
+
+
+class MySubClass
+       super MyClass
+       super Interface
+       super AbstractClass
+
+       redef fun foo(x: Int, y: Float)
+       do
+               if x == 10 then print "Error"
+       end
+
+       redef fun baz(x: Int, y: Float)
+       do
+
+       end
+
+       redef fun bar(x: Int, y: Float)
+       do
+               if x < 1 then print "Error"
+       end
+end
+
+
+var first = new MySubClass
+first.foo(11,2.0) # Ok
+first.bar(1,10.0) # Ok
+first.baz(9,42.0) # Fail y > 10 (y = 42.0)
diff --git a/tests/contracts_add.nit b/tests/contracts_add.nit
new file mode 100644 (file)
index 0000000..5ab7621
--- /dev/null
@@ -0,0 +1,55 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Verification if it's possible to add a other contract type on existing method.
+# Only the expect contract display a warning when none expect contract are not defined at the introduction
+
+class MyClass
+       fun foo(x: Int)
+       is
+               expects(x == 1)
+       do
+               x=1
+       end
+
+       fun bar(x: Float): Bool
+       is
+               ensures(result)
+       do
+               return true
+       end
+end
+
+class MyClass2
+       super MyClass
+
+       redef fun foo(x: Int)
+       is
+               ensures(x == 0)
+       do
+               x=0
+       end
+
+       redef fun bar(x: Float)
+       is
+               expects(x == 1)
+       do
+               return true
+       end
+end
+
+var first = new MyClass2
+first.foo(1)
+first.bar(1.0)
+first.foo(0)# Fail because the expect is x == 1
diff --git a/tests/contracts_attributs.nit b/tests/contracts_attributs.nit
new file mode 100644 (file)
index 0000000..504f0f9
--- /dev/null
@@ -0,0 +1,52 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Verification if it's possible to define a contract with a call of an attribute and a method call
+
+class MyClass
+
+       var bar = 10
+
+       fun foo(x: Int)
+       is
+               expects(bar == 10)
+               ensures(x > 0)
+       do
+               if bar != 10 then print "Error"
+       end
+end
+
+class MyClass2
+
+       var my_class: MyClass
+
+       fun baz: Bool
+       do
+               return false
+       end
+
+       fun foo(bool: Bool)
+       is
+               expects(not self.baz)
+               ensures(my_class.bar == 11)
+       do
+               if baz then print "Error"
+               my_class.bar = 11
+       end
+end
+
+var first = new MyClass
+first.foo(1) # Ok
+var second = new MyClass2(first)
+second.foo(false) # Ok
diff --git a/tests/contracts_constructor.nit b/tests/contracts_constructor.nit
new file mode 100644 (file)
index 0000000..72eb9a9
--- /dev/null
@@ -0,0 +1,27 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test the contract on constructor.
+
+class MyClass
+       init construct(test: Int)
+       is
+               expects(test > 10)
+       do
+
+       end
+end
+
+var first = new MyClass.construct(13)
+var second = new MyClass.construct(9)
diff --git a/tests/contracts_ensures.nit b/tests/contracts_ensures.nit
new file mode 100644 (file)
index 0000000..24a6ffc
--- /dev/null
@@ -0,0 +1,38 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Verification if it's possible to define a simple ensures contract.
+
+class MyClass
+       fun foo(x: Int)
+       is
+               ensures(x > 0)
+       do
+
+       end
+end
+
+class MyClass2
+       fun foo(bool: Bool)
+       is
+               ensures(not bool)
+       do
+
+       end
+end
+
+var first = new MyClass
+first.foo(1)
+var second = new MyClass2
+second.foo(true) #Fail because the ensure is bool == false
diff --git a/tests/contracts_ensures_1.nit b/tests/contracts_ensures_1.nit
new file mode 100644 (file)
index 0000000..be6798e
--- /dev/null
@@ -0,0 +1,38 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Verification if the contract is inherited
+
+class MyClass
+       fun foo(x: Int)
+       is
+               ensures(x > 0)
+       do
+               print "Good"
+       end
+end
+
+class SubClass
+       super MyClass
+
+       redef fun foo(x: Int) do
+               print "Fail"
+       end
+end
+
+var first = new MyClass
+first.foo(1)
+var sub = new SubClass
+# Check if the contract is inherited
+sub.foo(0) # Fail
\ No newline at end of file
diff --git a/tests/contracts_ensures_2.nit b/tests/contracts_ensures_2.nit
new file mode 100644 (file)
index 0000000..c102e33
--- /dev/null
@@ -0,0 +1,41 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Check if the expects contract is redef with `and`
+
+class MyClass
+       fun foo(x: Int, y: Float)
+       is
+               ensures(x > 0)
+       do
+               print "Good"
+       end
+end
+
+class SubClass
+       super MyClass
+
+       redef fun foo(x: Int, y: Float)
+       is
+               ensures(y == 1.2)
+       do
+               print "Good"
+       end
+end
+
+var first = new MyClass
+first.foo(1,1.2) # OK
+var sub = new SubClass
+sub.foo(1,1.2) # OK
+sub.foo(1,1.0) # Fail
\ No newline at end of file
diff --git a/tests/contracts_ensures_3.nit b/tests/contracts_ensures_3.nit
new file mode 100644 (file)
index 0000000..5557ef2
--- /dev/null
@@ -0,0 +1,28 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Check the ensures with a contract on a result (the return result of the method)
+
+class MyClass
+       fun foo(x: Int): Int
+       is
+               ensures(result > 0)
+       do
+               return x
+       end
+end
+
+var first = new MyClass
+first.foo(1) # OK
+first.foo(0) # FAIL
diff --git a/tests/contracts_ensures_4.nit b/tests/contracts_ensures_4.nit
new file mode 100644 (file)
index 0000000..d5cd82d
--- /dev/null
@@ -0,0 +1,40 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Check the result with the super call
+
+class MyClass
+       fun foo(x: Int): Bool
+       is
+               ensures(x > 0, result)
+       do
+               return true
+       end
+end
+
+class MySubClass
+       super MyClass
+
+       redef fun foo(x: Int)
+       is
+               ensures(not result)
+       do
+               return super
+       end
+end
+
+var first = new MyClass
+first.foo(1)
+var second = new MySubClass
+second.foo(2) #Fail
diff --git a/tests/contracts_ensures_sequence.nit b/tests/contracts_ensures_sequence.nit
new file mode 100644 (file)
index 0000000..ab66e57
--- /dev/null
@@ -0,0 +1,38 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class MyClass
+       fun foo(x: Int, y: Float)
+       is
+               ensures(x > 2)
+       do
+
+       end
+end
+
+class MySubClass
+       super MyClass
+
+       redef fun foo(x: Int, y: Float)
+       is
+               ensures(y > 1.0)
+       do
+
+       end
+end
+
+var first = new MyClass
+first.foo(2, 1.1)
+var second = new MySubClass
+second.foo(1, 0.5) #Fail
diff --git a/tests/contracts_error.nit b/tests/contracts_error.nit
new file mode 100644 (file)
index 0000000..d103f58
--- /dev/null
@@ -0,0 +1,31 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test the error if the annotation parameter is not an good expression (ie is not a comparisons or a method calls returning bouleans)
+
+class MyClass
+
+       fun bar_no_return do end
+
+       fun foo(x: Int)
+       is
+               expects(bar_no_return)
+               ensures(assert x == 1)
+       do
+               x = 0
+       end
+end
+
+var first = new MyClass
+first.foo(1)
diff --git a/tests/contracts_expects.nit b/tests/contracts_expects.nit
new file mode 100644 (file)
index 0000000..ec2f4d2
--- /dev/null
@@ -0,0 +1,38 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Verification if it's possible to define a simple expects contract.
+
+class MyClass
+       fun foo(x: Int)
+       is
+               expects(x > 0)
+       do
+               if x <= 0 then print "FAIL"
+       end
+end
+
+class MyClass2
+       fun foo(bool: Bool)
+       is
+               expects(not bool)
+       do
+               if bool then print "FAIL"
+       end
+end
+
+var first = new MyClass
+first.foo(1) # OK
+var second = new MyClass2
+second.foo(false) # OK
diff --git a/tests/contracts_expects_1.nit b/tests/contracts_expects_1.nit
new file mode 100644 (file)
index 0000000..eb923ab
--- /dev/null
@@ -0,0 +1,37 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Check if the contract is inherited
+
+class MyClass
+       fun foo(x: Int)
+       is
+               expects(x > 0)
+       do
+               if x <= 0 then print "FAIL"
+       end
+end
+
+class SubClass
+       super MyClass
+
+       redef fun foo(x: Int) do
+               if x <= 0 then print "FAIL"
+       end
+end
+
+var first = new MyClass
+first.foo(1) # OK
+var sub = new SubClass
+sub.foo(0) # Fail
diff --git a/tests/contracts_expects_2.nit b/tests/contracts_expects_2.nit
new file mode 100644 (file)
index 0000000..3af9ebb
--- /dev/null
@@ -0,0 +1,41 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Check if the expects contract is redef with `or`
+
+class MyClass
+       fun foo(x: Int)
+       is
+               expects(x > 0)
+       do
+               if x <= 0 then print "FAIL"
+       end
+end
+
+class SubClass
+       super MyClass
+
+       redef fun foo(x: Int)
+       is
+               expects(x == 0)
+       do
+               if x < 0 then print "FAIL"
+       end
+end
+
+var first = new MyClass
+first.foo(1)
+var sub = new SubClass
+sub.foo(3) # OK
+sub.foo(0) # OK
diff --git a/tests/contracts_expects_3.nit b/tests/contracts_expects_3.nit
new file mode 100644 (file)
index 0000000..63bf318
--- /dev/null
@@ -0,0 +1,49 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Check if the adding expects contract in a redefinition on an existing method return a warning
+
+class MyClass
+       fun foo(x: Int)
+       do
+               print "Good"
+       end
+end
+
+class SubClass
+       super MyClass
+
+       redef fun foo(x: Int)
+       is
+               expects(x == 0)
+       do
+               if x != 0 then print "Good"
+       end
+end
+
+class SubSubClass
+       super SubClass
+
+       redef fun foo(x: Int)
+       do
+               if x != 0 then print "Good"
+       end
+end
+
+var first = new MyClass
+first.foo(1) # OK
+var sub = new SubClass
+sub.foo(0) # OK
+var subsub = new SubSubClass
+subsub.foo(1) # Ok
diff --git a/tests/contracts_inheritance.nit b/tests/contracts_inheritance.nit
new file mode 100644 (file)
index 0000000..015d483
--- /dev/null
@@ -0,0 +1,58 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Check usage of contract on a diamond inheritance
+
+class ArrayInt
+       super Array[Int]
+
+       fun toto(e: Int)
+       do
+               print "toto ArrayInt"
+       end
+end
+
+
+class MyArrayInt
+       super ArrayInt
+
+       redef fun toto(e)
+       is
+               ensures(e == 12)
+       do
+               print "toto MyArrayInt"
+               super e
+       end
+end
+
+class MyArrayInt2
+       super ArrayInt
+
+       redef fun toto(e)
+       is
+               ensures(e == 11)
+       do
+               print "toto MyArrayInt2"
+               super e
+               print "Good"
+       end
+end
+
+class MySubArray
+       super MyArrayInt
+       super MyArrayInt2
+end
+
+var test = new MySubArray
+test.toto(11)# fail contract on MyArrayInt define e == 12
diff --git a/tests/contracts_same_contract.nit b/tests/contracts_same_contract.nit
new file mode 100644 (file)
index 0000000..a34005f
--- /dev/null
@@ -0,0 +1,29 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test when to same contracts are use in a same method definition
+
+class MyClass
+
+       fun foo(x: Int)
+       is
+               expects(x == 10)
+               expects(x >= 10)
+       do
+               x = 0
+       end
+end
+
+var first = new MyClass
+first.foo(10)
diff --git a/tests/sav/contracts.res b/tests/sav/contracts.res
new file mode 100644 (file)
index 0000000..6be0568
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert 'expects' failed (contracts.nit:31)
diff --git a/tests/sav/contracts_abstract.res b/tests/sav/contracts_abstract.res
new file mode 100644 (file)
index 0000000..b69a60e
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert 'ensures' failed (contracts_abstract.nit:26)
diff --git a/tests/sav/contracts_add.res b/tests/sav/contracts_add.res
new file mode 100644 (file)
index 0000000..f74e59a
--- /dev/null
@@ -0,0 +1,2 @@
+contracts_add.nit:46,3--17: Useless contract: No contract defined at the introduction of the method
+Runtime error: Assert 'ensures' failed (contracts_add.nit:39)
diff --git a/tests/sav/contracts_attributs.res b/tests/sav/contracts_attributs.res
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/sav/contracts_constructor.res b/tests/sav/contracts_constructor.res
new file mode 100644 (file)
index 0000000..180b9c7
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert 'expects' failed (contracts_constructor.nit:20)
diff --git a/tests/sav/contracts_ensures.res b/tests/sav/contracts_ensures.res
new file mode 100644 (file)
index 0000000..b710ec9
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert 'ensures' failed (contracts_ensures.nit:29)
diff --git a/tests/sav/contracts_ensures_1.res b/tests/sav/contracts_ensures_1.res
new file mode 100644 (file)
index 0000000..99f2817
--- /dev/null
@@ -0,0 +1,3 @@
+Runtime error: Assert 'ensures' failed (contracts_ensures_1.nit:20)
+Good
+Fail
diff --git a/tests/sav/contracts_ensures_2.res b/tests/sav/contracts_ensures_2.res
new file mode 100644 (file)
index 0000000..a778c12
--- /dev/null
@@ -0,0 +1,4 @@
+Runtime error: Assert 'ensures' failed (contracts_ensures_2.nit:31)
+Good
+Good
+Good
diff --git a/tests/sav/contracts_ensures_3.res b/tests/sav/contracts_ensures_3.res
new file mode 100644 (file)
index 0000000..8bf6416
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert 'ensures' failed (contracts_ensures_3.nit:20)
diff --git a/tests/sav/contracts_ensures_4.res b/tests/sav/contracts_ensures_4.res
new file mode 100644 (file)
index 0000000..c0a39ad
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert 'ensures' failed (contracts_ensures_4.nit:31)
diff --git a/tests/sav/contracts_ensures_5.res b/tests/sav/contracts_ensures_5.res
new file mode 100644 (file)
index 0000000..fedb42c
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert 'ensures' failed (contracts_ensures_5.nit:29)
diff --git a/tests/sav/contracts_ensures_sequence.res b/tests/sav/contracts_ensures_sequence.res
new file mode 100644 (file)
index 0000000..e462610
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert 'ensures' failed (contracts_ensures_sequence.nit:18)
diff --git a/tests/sav/contracts_error.res b/tests/sav/contracts_error.res
new file mode 100644 (file)
index 0000000..560ce7a
--- /dev/null
@@ -0,0 +1,2 @@
+contracts_error.nit:23,11--23: Error: expected an expression.
+contracts_error.nit:24,11--23: Error: expected an expression.
diff --git a/tests/sav/contracts_expects.res b/tests/sav/contracts_expects.res
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/sav/contracts_expects_1.res b/tests/sav/contracts_expects_1.res
new file mode 100644 (file)
index 0000000..1cfc3f6
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert 'expects' failed (contracts_expects_1.nit:20)
diff --git a/tests/sav/contracts_expects_2.res b/tests/sav/contracts_expects_2.res
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/sav/contracts_expects_3.res b/tests/sav/contracts_expects_3.res
new file mode 100644 (file)
index 0000000..622d67c
--- /dev/null
@@ -0,0 +1,3 @@
+contracts_expects_3.nit:29,3--17: Useless contract: No contract defined at the introduction of the method
+Good
+Good
diff --git a/tests/sav/contracts_inheritance.res b/tests/sav/contracts_inheritance.res
new file mode 100644 (file)
index 0000000..65c6bf5
--- /dev/null
@@ -0,0 +1,6 @@
+contracts_inheritance.nit:58,6--9: Warning: conflicting property definitions for property `toto` in `MySubArray`: contracts_inheritance$MyArrayInt$toto contracts_inheritance$MyArrayInt2$toto
+Runtime error: Assert 'ensures' failed (contracts_inheritance.nit:32)
+toto MyArrayInt2
+toto MyArrayInt
+toto ArrayInt
+Good
diff --git a/tests/sav/contracts_same_contract.res b/tests/sav/contracts_same_contract.res
new file mode 100644 (file)
index 0000000..cf99218
--- /dev/null
@@ -0,0 +1 @@
+contracts_same_contract.nit:22,3--18: The method already has a defined `expects` contract at line 21
index ad80c05..a04e1b8 100644 (file)
@@ -5,7 +5,7 @@ _DUMMY_TOOL()
        COMPREPLY=()
        cur="${COMP_WORDS[COMP_CWORD]}"
        prev="${COMP_WORDS[COMP_CWORD-1]}"
-       opts="--warn --warning --quiet --stop-on-first-error --keep-going --no-color --log --log-dir --nit-dir --help --version --set-dummy-tool --verbose --bash-completion --stub-man --option-a --option-b"
+       opts="--warn --warning --quiet --stop-on-first-error --keep-going --no-color --log --log-dir --nit-dir --help --version --set-dummy-tool --verbose --bash-completion --stub-man --no-contract --full-contract --option-a --option-b"
        if [[ ${cur} == -* ]] ; then
                COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
                return 0
index f7e7da5..9d4104e 100644 (file)
@@ -12,6 +12,8 @@ Test for ToolContext, try --bash-completion.
   -h, -?, --help          Show Help (This screen)
   --version               Show version and exit
   -v, --verbose           Additional messages from the tool
+  --no-contract           Disable the contracts usage
+  --full-contract         Enable all contracts usage
   -a, --option-a          option a, do nothing
   -b, --option-b          option b, do nothing
   -c                      option c, do nothing