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"
callsite
to see if the target mpropdef
has a contract.
nitc :: contracts $ AMethPropdef
A definition of all kind of method (including constructors)nitc :: contracts $ ASignature
A signature in a method definition. eg(x,y:X,z:Z):T
nitc :: contracts $ BottomMContract
The root of all contracts where the call is after the execution of the original method (invariant
and ensure
).
nitc :: contracts $ AMethPropdef
A definition of all kind of method (including constructors)nitc :: contracts $ ASignature
A signature in a method definition. eg(x,y:X,z:Z):T
nitc :: contracts $ BottomMContract
The root of all contracts where the call is after the execution of the original method (invariant
and ensure
).
callsite
to see if the target mpropdef
has a contract.
Serializable::inspect
to show more useful information
nitc :: modelbuilder
more_collections :: more_collections
Highly specific, but useful, collections-related classes.serialization :: serialization_core
Abstract services to serialize Nit objects to different formatsnitc :: toolcontext
Common command-line tool infrastructure than handle options and error messagescore :: union_find
union–find algorithm using an efficient disjoint-set data structurenitc :: astbuilder
Instantiation and transformation of semantic nodes in the AST of expressions and statementsnitc :: light_only
Compiler support for the light FFI only, detects unsupported usage of callbacksnitc
.
nitc :: nitmetrics
A program that collects various metrics on nit programs and librariesnitc :: separate_erasure_compiler
Separate compilation of a Nit program with generic type erasureclone
method of the astbuilder tool
# 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 parse_annotations
import phase
import semantize
intrude import model_contract
intrude import astbuilder
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
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
redef fun process_mainmodule(mainmodule: MModule, given_mmodules: SequenceRead[MModule]) do
# Visit all loaded modules `toolcontext.nmodules` to do contract weaving
for nmodule in toolcontext.modelbuilder.nmodules do
nmodule.do_weaving_contracts(self.toolcontext)
end
end
end
redef class AModule
# Entry point to generate the entire contract infrastructure.
# Once this method is called we must call the `do_weaving_contracts` method (see it for more information).
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, ast_builder)
contract_visitor.enter_visit(self)
end
# Entry point to execute the weaving in order to redirect the calls to the contract sides if it's needed.
fun do_weaving_contracts(toolcontext: ToolContext)
do
var ast_builder = new ASTBuilder(mmodule.as(not null))
var callsite_visitor = new CallSiteVisitor(toolcontext, ast_builder)
callsite_visitor.enter_visit(self)
end
end
# Visitor to build all contracts.
private class ContractsVisitor
super Visitor
# Instance of the toolcontext
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?
# This attribute has the same value as the `is_intro` of the propdef attached to the contract.
# Note : For MClassDef `is_intro_contract == false`. This is due to the fact that a method for checking invariants is systematically added to the root object class.
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
# 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)
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_annotations: Array[AAnnotation], mcontract: MContract, mclassdef: MClassDef)
do
var n_conditions = new Array[AExpr]
# Retrieving the expression provided in the annotation
for n_annotation in n_annotations do n_conditions.add n_annotation.construct_condition(self)
if is_intro_contract then
# Create new contract method
mcontract.create_intro_contract(self, n_conditions, mclassdef)
else
# Create a redef of contract to take in consideration the new condition
mcontract.create_subcontract(self, n_conditions, mclassdef)
end
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_propdef: APropdef is noinit
redef fun visit(node)
do
node.check_callsite(self)
node.visit_all(self)
end
# Check if the callsite is bound on a property with a contract.
# If the property is linked to a contract a new callsite will be created towards the correct facet,
# in the other case the returned callsite wall be the same as the given `callsite`
private fun drive_callsite_to_contract(callsite: CallSite): CallSite
do
var contract_facet = callsite.mproperty.mcontract_facet
var visited_mpropdef = visited_propdef.mpropdef
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
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. If there are several parameters, the result is an `AAndExpr`
# Example:
# the contract `ensure(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)
n_condition.location = self.location
return n_condition
end
end
redef class MContract
# Should contract be called?
# return `true` if the contract needs to be called.
private fun is_called(v: ContractsVisitor, mpropdef: MPropDef): Bool
do
return v.toolcontext.opt_full_contract.value
end
# Method use to diplay warning when the contract is not present at the introduction
private fun no_intro_contract(v: ContractsVisitor, a: Array[AAnnotation])do end
# Creating specific inheritance block contract
#
# `super_args` : Correspond to the `super` call arguments
private fun create_inherit_nblock(v: ContractsVisitor, n_conditions: Array[AExpr], super_args: Array[AExpr]): ABlockExpr is abstract
# Method to adapt the given `n_mpropdef.n_block` to the contract
private fun adapt_method_to_contract(v: ContractsVisitor, mfacet: MFacet, n_mpropdef: AMethPropdef) is abstract
# Create and return an adapted `MSignature` specifically for the contract in fonction of the given `m_signature`
private fun adapt_specific_msignature(m_signature: MSignature): MSignature do return m_signature.adapt_to_contract
# Create and return an adapted `ASignature` specifically for the contract in fonction of the given `n_signature`
private fun adapt_specific_nsignature(n_signature: ASignature): ASignature do return n_signature.adapt_to_contract
# Adapt the `m_signature` to the contract
# If `m_signature == null` return a new `MSignature` else it calls a specific adapt method see `adapt_specific_msignature`
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 `n_signature == null` return a new `ASignature` else it calls a specific adapt method see `adapt_specific_nsignature`
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 the initial contract (intro)
# All contracts have the same implementation for the introduction.
#
# Example:
# ~~~nitish
# fun contrat([...])
# do
# assert contract_condition
# end
# ~~~
#
private fun create_intro_contract(v: ContractsVisitor, n_conditions: Array[AExpr], mclassdef: MClassDef)
do
var n_block = v.ast_builder.make_block
for n_condition in n_conditions do
# Create a new tid to set the name of the assert for more explicit error
var tid = new TId.init_tk(n_condition.location)
tid.text = "{n_condition.location.text}"
var n_assert = v.ast_builder.make_assert(tid, n_condition, null)
# Define the assert location to reference the annoation
n_assert.location = n_condition.location
n_block.add n_assert
end
make_contract(v, n_block, mclassdef)
end
# Create a contract to check the old (super call) and the new conditions
#
# Example:
# ~~~nitish
# fun contrat([...])
# do
# super # Call the old contracts
# assert new_condition
# end
# ~~~
#
private fun create_subcontract(v: ContractsVisitor, n_conditions: Array[AExpr], mclassdef: MClassDef)
do
var args = v.n_signature.make_parameter_read(v.ast_builder)
var n_block = v.ast_builder.make_block
n_block = self.create_inherit_nblock(v, n_conditions, args)
make_contract(v, n_block, mclassdef)
end
# Build a new contract method with a specific block `n_block` in a specified `mclassdef`
private fun make_contract(v: ContractsVisitor, n_block: AExpr, mclassdef: MClassDef)
do
var n_contractdef = v.toolcontext.modelbuilder.create_method_from_property(self, mclassdef, false, v.m_signature)
# Set the signature to keep the same variable
n_contractdef.n_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)
end
end
redef class MExpect
redef fun is_called(v: ContractsVisitor, mpropdef: MPropDef): Bool
do
var main_package = v.mainmodule.mpackage
var actual_package = mpropdef.mclassdef.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 super or main_package == actual_package or condition_direct_arc
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.
#
# Example:
# ~~~nitish
# class A
# fun bar [...]
# fun _bar_expect([...])
# do
# [empty contract]
# end
# end
#
# redef class A
# redef fun bar is expect(contract_condition)
# redef fun _bar_expect([...])
# do
# if (contract_condition) then return
# super
# end
# end
# ~~~~
#
redef fun no_intro_contract(v: ContractsVisitor, a: Array[AAnnotation])
do
v.toolcontext.warning(a.first.location,"useless_contract","Useless contract: No contract defined at the introduction of the method")
end
redef fun create_inherit_nblock(v: ContractsVisitor, n_conditions: Array[AExpr], super_args: Array[AExpr]): ABlockExpr
do
var n_block = v.ast_builder.make_block
for n_condition in n_conditions 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
if_block.location = n_condition.location
n_block.add if_block
end
n_block.add v.ast_builder.make_super_call(super_args)
return n_block
end
redef fun adapt_method_to_contract(v: ContractsVisitor, mfacet: MFacet, n_mpropdef: AMethPropdef)
do
var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_mpropdef, self, true)
var args = n_mpropdef.n_signature.make_parameter_read(v.ast_builder)
var n_callexpect = v.ast_builder.make_call(new ASelfExpr, 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 v.encapsulated_contract_call(n_mpropdef, [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
mfacet.has_applied_expect = true
end
end
redef class BottomMContract
redef fun is_called(v: ContractsVisitor, mpropdef: MPropDef): Bool
do
return super or v.mainmodule.mpackage == mpropdef.mclassdef.mmodule.mpackage
end
redef fun create_inherit_nblock(v: ContractsVisitor, n_conditions: Array[AExpr], super_args: Array[AExpr]): ABlockExpr
do
# Define contract block
var n_block = v.ast_builder.make_block
var super_call = v.ast_builder.make_super_call(super_args)
n_block.add super_call
for n_condition in n_conditions do
var tid = new TId.init_tk(n_condition.location)
tid.text = "{n_condition.location.text}"
# Creating the assert expression with the new condition
var n_assert = v.ast_builder.make_assert(tid, n_condition)
n_assert.location = n_condition.location
n_block.add n_assert
end
return n_block
end
# Inject the `result` variable into the `n_block` of the given n_mpropdef`.
#
# The purpose of the variable is to capture return values to use it in contracts.
private fun inject_result(v: ContractsVisitor, n_mpropdef: AMethPropdef, ret_type: MType): Variable
is
expect(n_mpropdef.n_signature.n_type != null)
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_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
var 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
redef class MEnsure
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_method_to_contract(v: ContractsVisitor, mfacet: MFacet, 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)
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
# Inject the variable result
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_call_contract = v.ast_builder.make_call(n_self, callsite, args)
actual_block.add_all([v.encapsulated_contract_call(n_mpropdef, [n_call_contract]), return_expr])
else
# build the call to the contract method
var n_call_contract = v.ast_builder.make_call(n_self, callsite, args)
actual_block.add v.encapsulated_contract_call(n_mpropdef, [n_call_contract])
end
n_mpropdef.do_all(v.toolcontext)
mfacet.has_applied_ensure = true
end
end
redef class MMethod
# Define contract facet for MMethod in the given mclassdef. The facet represents the entry point with contracts (expect, ensure) of the method.
# If a contract is given adapt the contract facet.
#
# `classdef`: Indicates the class where we want to introduce our facet
# `exist_contract`: Indicates if it is necessary to define a new facet for the contract. If `exist_contract_facet and exist_contract` it's not necessary to add a facet.
#
# Exemple:
# ~~~nitish
# from:
# classe A
# i :Int
# fun add_one is ensure(old(i) + 1 == i)
# end
# to:
# classe A
# fun add_one is ensure(old(i) + 1 == i)
#
# # The contract facet
# fun contract_add_one do
# add_one
# ensure_add_one(old_add_one)
# end
# end
# ~~
private fun define_contract_facet(v: ContractsVisitor, classdef: MClassDef, mcontract: nullable MContract)
do
var exist_contract_facet = has_contract_facet
var contract_facet = build_contract_facet
# Do nothing the contract and the contract facet already exist
if mcontract != null and mcontract.is_already_applied(contract_facet) then return
var n_contract_facet: AMethPropdef
if not exist_contract_facet then
# If has no contract facet in intro just create it
if classdef != intro_mclassdef then
var n_intro_face = create_facet(v, intro_mclassdef, contract_facet, self)
n_intro_face.location = self.intro.location
n_intro_face.do_all(v.toolcontext)
end
n_contract_facet = create_facet(v, classdef, contract_facet, self)
else
# Check if the contract facet already exist in this context (in this classdef)
if classdef.mpropdefs_by_property.has_key(contract_facet) then
# get the definition
n_contract_facet = v.toolcontext.modelbuilder.mpropdef2node(classdef.mpropdefs_by_property[contract_facet]).as(AMethPropdef)
else
# create a new contract facet definition
n_contract_facet = create_facet(v, classdef, contract_facet, self)
var block = v.ast_builder.make_block
# super call to the contract facet
var args = n_contract_facet.n_signature.make_parameter_read(v.ast_builder)
var n_super_call = v.ast_builder.make_super_call(args)
# verification for add a return or not
if self.intro.msignature.return_mtype != null then
block.add(v.ast_builder.make_return(n_super_call))
else
block.add(n_super_call)
end
n_contract_facet.n_block = block
end
end
if mcontract != null then mcontract.adapt_method_to_contract(v, contract_facet, n_contract_facet)
n_contract_facet.location = v.current_location
n_contract_facet.do_all(v.toolcontext)
end
# Method to create a facet of the method.
# See `define_contract_facet` for more information about two types of facets.
#
# `called` : is the property to call in this facet.
private fun create_facet(v: ContractsVisitor, classdef: MClassDef, facet: MFacet, called: MMethod): AMethPropdef
is
expect( called.is_same_instance(self) or called.is_same_instance(self.mcontract_facet) )
do
# Defines the contract facet is an init or not
# it is necessary to use the contracts with in a constructor
facet.is_init = is_init
var n_contractdef = v.toolcontext.modelbuilder.create_method_from_property(facet, classdef, false, self.intro.msignature)
# 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
# Arguments to call the `called` property
var args: Array[AExpr]
args = n_contractdef.n_signature.make_parameter_read(v.ast_builder)
var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_contractdef, called, true)
var n_call = v.ast_builder.make_call(new ASelfExpr, callsite, args)
if self.intro.msignature.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
end
redef class MMethodDef
# 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_annotations: Array[AAnnotation], mcontract: MContract, exist_contract: Bool)
do
v.define_signature(mcontract, n_signature, mproperty.intro.msignature)
if not exist_contract and not is_intro then no_intro_contract(v, mcontract, n_annotations)
v.build_contract(n_annotations, mcontract, mclassdef)
# Check if the contract must be called to know if it's needed to construct the facet
if mcontract.is_called(v, self) then mproperty.define_contract_facet(v, mclassdef, mcontract)
end
# Create a contract on the introduction classdef of the property.
# Display an warning message if necessary
private fun no_intro_contract(v: ContractsVisitor, mcontract: MContract, n_annotations: Array[AAnnotation])
do
v.toolcontext.modelbuilder.create_method_from_property(mcontract, mcontract.intro_mclassdef, false, v.m_signature)
mcontract.no_intro_contract(v, n_annotations)
end
# Apply the `no_contract` annotation to the contract. This method removes the inheritance by adding an empty contract method.
# Display a warning if the annotation is not needed
private fun no_contract_apply(v: ContractsVisitor, n_signature: ASignature)
do
var mensure = mproperty.mensure
var mexpect = mproperty.mexpect
if mensure == null and mexpect == null then
v.toolcontext.warning(location, "useless_nocontract", "Useless `no_contract`, no contract was declared for `{name}`. Remove the `no_contract`")
end
if mensure != null then
# Add an empty ensure method to replace the actual definition
v.toolcontext.modelbuilder.create_method_from_property(mensure, mclassdef, false, mensure.intro.msignature)
end
if mexpect != null then
# Add an empty expect method to replace the actual definition
v.toolcontext.modelbuilder.create_method_from_property(mexpect, mclassdef, false, mexpect.intro.msignature)
end
end
end
redef class APropdef
redef fun check_callsite(v)
do
v.visited_propdef = self
end
end
redef class AMethPropdef
# 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
check_annotation(v)
if not mpropdef.is_intro then check_redef(v)
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)
do
var annotations_expect = get_annotations("expect")
var annotations_ensure = get_annotations("ensure")
var annotation_no_contract = get_annotations("no_contract")
if (not annotations_expect.is_empty or not annotations_ensure.is_empty) and not annotation_no_contract.is_empty then
v.toolcontext.error(location, "The new contract definition is not correct when using `no_contract`. Remove the contract definition or the `no_contract`")
return
end
var nsignature = n_signature or else new ASignature
if not annotation_no_contract.is_empty then
mpropdef.no_contract_apply(v, nsignature.clone)
return
end
if not annotations_expect.is_empty then
var exist_contract = mpropdef.mproperty.has_expect
mpropdef.construct_contract(v, nsignature.clone, annotations_expect, mpropdef.mproperty.build_expect, exist_contract)
end
if not annotations_ensure.is_empty then
var exist_contract = mpropdef.mproperty.has_ensure
mpropdef.construct_contract(v, nsignature.clone, annotations_ensure, mpropdef.mproperty.build_ensure, exist_contract)
end
end
# Verification if the method have an inherited contract to apply it.
private fun check_redef(v: ContractsVisitor)
do
var mexpect = mpropdef.mproperty.mexpect
var mensure = mpropdef.mproperty.mensure
var mcontract_facet = mpropdef.mproperty.mcontract_facet
if mexpect != null then
if mcontract_facet != null and mcontract_facet.has_applied_expect then return
if mexpect.is_called(v, mpropdef.as(not null)) then mpropdef.mproperty.define_contract_facet(v, mpropdef.mclassdef, mexpect)
end
if mensure != null then
if mcontract_facet != null and mcontract_facet.has_applied_ensure then return
if mensure.is_called(v, mpropdef.as(not null)) then mpropdef.mproperty.define_contract_facet(v, mpropdef.mclassdef, mensure)
end
end
end
redef class MSignature
# Adapt signature for an contract
#
# The returned `MSignature` is the copy of `self` without return type.
private fun adapt_to_contract: MSignature do return new MSignature(mparameters.to_a, null)
# Adapt signature for a ensure contract
#
# The returned `MSignature` is the copy of `self` without return type.
# The return type is replaced by a new parameter `result`
private fun adapt_to_ensurecondition: MSignature
do
var rtype = return_mtype
var msignature = adapt_to_contract
if rtype != null then
msignature.mparameters.add(new MParameter("result", rtype, false))
end
return msignature
end
# The returned `MSignature` is the exact copy of `self`.
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
# Create a new ASignature adapted for contract
#
# The returned `ASignature` is the copy of `self` without return type.
private fun adapt_to_contract: ASignature
do
var adapt_nsignature = self.clone
if adapt_nsignature.n_type != null then adapt_nsignature.n_type.detach
return adapt_nsignature
end
# Create a new ASignature adapted for ensure
#
# The returned `ASignature` is the copy of `self` without return type.
# The return type is replaced by a new parameter `result`
private fun adapt_to_ensurecondition: ASignature do
var nsignature = adapt_to_contract
if ret_type != null then
var variable = new Variable("result")
variable.declared_type = ret_type
nsignature.n_params.add new AParam.make(variable, ret_type.create_ast_representation)
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_callsite_to_contract(actual_callsite)
# Set the signature mapping with the old value, this avoids having to re-check the callsite.
callsite.signaturemap = actual_callsite.signaturemap
end
end
end
redef class ANewExpr
redef fun check_callsite(v)
do
var actual_callsite = callsite
if actual_callsite != null then
callsite = v.drive_callsite_to_contract(actual_callsite)
# Set the signature mapping with the old value, this avoids having to re-check the callsite
callsite.signaturemap = actual_callsite.signaturemap
end
end
end
src/contracts.nit:15,1--855,3