contracts: Remove useless return parameter
[nit.git] / src / contracts.nit
index 78803a2..71f4820 100644 (file)
 # 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 model_contract
+intrude import astbuilder
 intrude import modelize_property
 intrude import scope
 intrude import typing
@@ -56,11 +57,12 @@ redef class AModule
        #   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 ast_builder = new ASTBuilder(mmodule.as(not null))
                #
-               var contract_visitor = new ContractsVisitor(toolcontext, toolcontext.modelbuilder.identified_modules.first, self, new ASTBuilder(mmodule.as(not null)))
+               var contract_visitor = new ContractsVisitor(toolcontext, toolcontext.modelbuilder.identified_modules.first, self, ast_builder)
                contract_visitor.enter_visit(self)
                #
-               var callsite_visitor = new CallSiteVisitor(toolcontext)
+               var callsite_visitor = new CallSiteVisitor(toolcontext, ast_builder)
                callsite_visitor.enter_visit(self)
        end
 end
@@ -101,6 +103,11 @@ private class ContractsVisitor
        # is `no_contract` annotation was found
        var find_no_contract = false
 
+       # The reference to the `in_contract` attribute.
+       # This attribute is used to disable contract verification when you are already in a contract verification.
+       # Keep the `in_contract` attribute to avoid searching at each contrat
+       var in_contract_attribute: nullable MAttribute = null
+
        redef fun visit(node)
        do
                node.create_contracts(self)
@@ -117,7 +124,7 @@ private class ContractsVisitor
        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
+       private fun build_contract(n_annotation: AAnnotation, mcontract: MContract, mclassdef: MClassDef)
        do
                self.current_location = n_annotation.location
                # Retrieving the expression provided in the annotation
@@ -125,15 +132,11 @@ private class ContractsVisitor
                var m_contractdef: AMethPropdef
                if is_intro_contract then
                        # Create new contract method
-                       m_contractdef = mcontract.create_intro_contract(self, n_condition, mclassdef)
+                       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)
+                       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
 
        # Verification if the construction of the contract is necessary.
@@ -165,18 +168,92 @@ private class ContractsVisitor
                return toolcontext.opt_full_contract.value or mainmodule.mpackage == actual_mmodule.mpackage
        end
 
+       # Inject the incontract attribute (`_in_contract_`) in the `Sys` class
+       # This attribute allows to check if the contract need to be executed
+       private fun inject_incontract_in_sys
+       do
+               # If the `in_contract_attribute` already know just return
+               if in_contract_attribute != null then return
+
+               var sys_class = toolcontext.modelbuilder.get_mclass_by_name(visited_module, mainmodule, "Sys")
+
+               # Try to get the `in_contract` property, if it has already defined in a previously visited module.
+               var in_contract_property = toolcontext.modelbuilder.try_get_mproperty_by_name(visited_module, sys_class.intro, "__in_contract_")
+               if in_contract_property != null then
+                       self.in_contract_attribute = in_contract_property.as(MAttribute)
+                       return
+               end
+
+               var bool_false = new AFalseExpr.make(mainmodule.bool_type)
+               var n_in_contract_attribute = toolcontext.modelbuilder.create_attribute_from_name("__in_contract_", sys_class.intro, mainmodule.bool_type, public_visibility).create_setter(toolcontext.modelbuilder, true).define_default(bool_false)
+
+               in_contract_attribute = n_in_contract_attribute.mpropdef.mproperty
+       end
+
+       # Return the `_in_contract_` attribute.
+       # If the attribute `_in_contract_` does not exist it's injected with `inject_incontract_in_sys`
+       private fun get_incontract: MAttribute
+       do
+               if self.in_contract_attribute == null then inject_incontract_in_sys
+               return in_contract_attribute.as(not null)
+       end
+
+       # Return an `AIfExpr` with the contract encapsulated by an `if` to check if it's already in a contract verification.
+       #
+       # Example:
+       # ~~~nitish
+       # class A
+       #       fun bar([...]) is except([...])
+       #
+       #       fun _contract_bar([...])
+       #       do
+       #               if not sys._in_contract_ then
+       #                       sys._in_contract_ = true
+       #                       _bar_expect([...])
+       #                       sys._in_contract_ = false
+       #               end
+       #               bar([...])
+       #       end
+       #
+       #       fun _bar_expect([...])
+       # end
+       # ~~~~
+       #
+       private fun encapsulated_contract_call(visited_method: AMethPropdef, call_to_contracts: Array[ACallExpr]): AIfExpr
+       do
+               var sys_property = toolcontext.modelbuilder.model.get_mproperties_by_name("sys").first.as(MMethod)
+               var callsite_sys = ast_builder.create_callsite(toolcontext.modelbuilder, visited_method, sys_property, true)
+
+               var incontract_attribute = get_incontract
+
+               var callsite_get_incontract = ast_builder.create_callsite(toolcontext.modelbuilder, visited_method, incontract_attribute.getter.as(MMethod), false)
+               var callsite_set_incontract = ast_builder.create_callsite(toolcontext.modelbuilder, visited_method, incontract_attribute.setter.as(MMethod), false)
+
+               var n_condition = ast_builder.make_not(ast_builder.make_call(ast_builder.make_call(new ASelfExpr, callsite_sys, null), callsite_get_incontract, null))
+
+               var n_if = ast_builder.make_if(n_condition, null)
+
+               var if_then_block = n_if.n_then.as(ABlockExpr)
+
+               if_then_block.add(ast_builder.make_call(ast_builder.make_call(new ASelfExpr, callsite_sys, null), callsite_set_incontract, [new ATrueExpr.make(mainmodule.bool_type)]))
+               if_then_block.add_all(call_to_contracts)
+               if_then_block.add(ast_builder.make_call(ast_builder.make_call(new ASelfExpr, callsite_sys, null), callsite_set_incontract, [new AFalseExpr.make(mainmodule.bool_type)]))
+
+               return n_if
+       end
 end
 
 # This visitor checks the `callsite` to see if the target `mpropdef` has a contract.
 private class CallSiteVisitor
        super Visitor
 
-
        # Instance of the toolcontext
        var toolcontext: ToolContext
 
+       var ast_builder: ASTBuilder
+
        # Actual visited method
-       var visited_method: APropdef is noinit
+       var visited_propdef: APropdef is noinit
 
        redef fun visit(node)
        do
@@ -188,18 +265,13 @@ private class CallSiteVisitor
        # 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.mproperty.mcontract_facet != null then
-                       var contract_facet = callsite.mproperty.mcontract_facet
-                       var visited_mpropdef = visited_method.mpropdef
-                       assert contract_facet != null and visited_mpropdef != null
+               var contract_facet = callsite.mproperty.mcontract_facet
+               var visited_mpropdef = visited_propdef.mpropdef
 
-                       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
-               return callsite
+               if visited_mpropdef isa MContract or visited_mpropdef isa MFacet then return callsite
+               if visited_mpropdef == null or contract_facet == null then return callsite
+
+               return ast_builder.create_callsite(toolcontext.modelbuilder, visited_propdef, contract_facet, callsite.recv_is_self)
        end
 end
 
@@ -223,12 +295,7 @@ redef class AAnnotation
        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
+redef class MContract
 
        # Define the name of the contract
        fun contract_name: String is abstract
@@ -282,7 +349,7 @@ abstract class MContract
        # end
        # ~~~
        #
-       private fun create_intro_contract(v: ContractsVisitor, n_condition: nullable AExpr, mclassdef: MClassDef): AMethPropdef
+       private fun create_intro_contract(v: ContractsVisitor, n_condition: nullable AExpr, mclassdef: MClassDef)
        do
                var n_block = v.ast_builder.make_block
                if n_condition != null then
@@ -291,34 +358,30 @@ abstract class MContract
                        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)
+               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
+       private fun create_subcontract(v: ContractsVisitor, ncondition: nullable AExpr, mclassdef: MClassDef)
        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)
+               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
+       private fun make_contract(v: ContractsVisitor, n_block: AExpr, mclassdef: MClassDef)
        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
+redef class MExpect
 
        # Define the name of the contract
        redef fun contract_name: String do return "expect"
@@ -384,9 +447,7 @@ class MExpect
        end
 end
 
-# The root of all contracts where the call is after the execution of the original method (`invariant` and `ensure`).
-abstract class BottomMContract
-       super MContract
+redef class BottomMContract
 
        redef fun create_nblock(v: ContractsVisitor, n_condition: AExpr, args: Array[AExpr]): ABlockExpr
        do
@@ -446,10 +507,7 @@ abstract class BottomMContract
        end
 end
 
-# A ensure (postcondition) representation
-# This method check if the called method respects the expectations of the caller.
-class MEnsure
-       super BottomMContract
+redef class MEnsure
 
        # Define the name of the contract
        redef fun contract_name: String do return "ensure"
@@ -526,59 +584,13 @@ redef class MClass
        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, "_ensure_{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, "_expect_{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(intro_mclassdef, "_contract_{name}", intro_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, classdef: MClassDef, mcontract: MContract, exist_contract: Bool)
        do
-               var exist_contract_facet = mproperty.check_exist_contract_facet(self)
+               var exist_contract_facet = mproperty.has_contract_facet
                if exist_contract_facet and exist_contract then return
 
                var contract_facet: AMethPropdef
@@ -615,8 +627,7 @@ redef class MMethodDef
        # Method to create a contract facet of the method
        private fun create_contract_facet(v: ContractsVisitor, classdef: MClassDef, n_signature: ASignature): AMethPropdef
        do
-               var contract_facet = mproperty.mcontract_facet
-               assert contract_facet != null
+               var contract_facet = mproperty.build_contract_facet
                # 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
@@ -654,7 +665,7 @@ redef class MMethodDef
                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)
+               v.build_contract(n_annotation, mcontract, mclassdef)
                check_contract_facet(v, n_signature.clone, mclassdef, mcontract, exist_contract)
                has_contract = true
        end
@@ -692,23 +703,12 @@ end
 redef class APropdef
        redef fun check_callsite(v)
        do
-               v.visited_method = self
+               v.visited_propdef = 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
@@ -737,12 +737,12 @@ redef class AMethPropdef
        do
                if n_annotation.name == "expect" then
                        if not v.check_usage_expect(mpropdef.mclassdef.mmodule) then return
-                       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)
+                       var exist_contract = mpropdef.mproperty.has_expect
+                       mpropdef.construct_contract(v, self.n_signature.as(not null), n_annotation, mpropdef.mproperty.build_expect, exist_contract)
                else if n_annotation.name == "ensure" then
                        if not v.check_usage_ensure(mpropdef.mclassdef.mmodule) then return
-                       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)
+                       var exist_contract = mpropdef.mproperty.has_ensure
+                       mpropdef.construct_contract(v, self.n_signature.as(not null), n_annotation, mpropdef.mproperty.build_ensure, exist_contract)
                else if n_annotation.name == "no_contract" then
                        # no_contract found set the flag to true
                        v.find_no_contract = true