1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Module to build contract
16 # This module provide extension of the modele to add contracts, the building phase and the "re-driving" to call the contract.
18 # FIXME Split the module in three parts: extension of the modele, building phase and the "re-driving"
21 import parse_annotations
24 intrude import model_contract
25 intrude import astbuilder
26 intrude import modelize_property
30 redef class ToolContext
31 # Parses contracts annotations.
32 var contracts_phase
: Phase = new ContractsPhase(self, [modelize_property_phase
,typing_phase
])
35 private class ContractsPhase
38 # The entry point of the contract phase
39 # In reality, the contract phase is executed on each module
40 # FIXME: Actually all method is checked to add method if any contract is inherited
41 redef fun process_nmodule
(nmodule
)do
42 # Check if the contracts are disabled
43 if toolcontext
.opt_no_contract
.value
then return
44 nmodule
.do_contracts
(self.toolcontext
)
49 # Compile all contracts
51 # The implementation of the contracts is done in two visits.
53 # - First step, the visitor analyzes and constructs the contracts
54 # for each class (invariant) and method (expect, ensure).
56 # - Second step the visitor analyzes each `ASendExpr` to see
57 # if the callsite calls a method with a contract. If this is
58 # the case the callsite is replaced by another callsite to the contract method.
59 fun do_contracts
(toolcontext
: ToolContext) do
60 var ast_builder
= new ASTBuilder(mmodule
.as(not null))
62 var contract_visitor
= new ContractsVisitor(toolcontext
, toolcontext
.modelbuilder
.identified_modules
.first
, self, ast_builder
)
63 contract_visitor
.enter_visit
(self)
65 var callsite_visitor
= new CallSiteVisitor(toolcontext
, ast_builder
)
66 callsite_visitor
.enter_visit
(self)
70 # Visitor to build all contracts.
71 private class ContractsVisitor
74 # Instance of the toolcontext
75 var toolcontext
: ToolContext
78 # It's strore to define if the contract need to be build depending on the selected policy `--no-contract` `--full-contract` or default
79 var mainmodule
: MModule
81 # Actual visited module
82 var visited_module
: AModule
84 var ast_builder
: ASTBuilder
86 # The `ASignature` of the actual build contract
87 var n_signature
: ASignature is noinit
89 # The `MSignature` of the actual build contract
90 var m_signature
: MSignature is noinit
92 # The `current_location` can corresponding of the annotation or method location.
93 var current_location
: Location is noinit
95 # Is the contrat is an introduction or not?
96 # This attribute has the same value as the `is_intro` of the propdef attached to the contract.
97 # 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.
98 var is_intro_contract
: Bool is noinit
100 # Actual visited class
101 var visited_class
: nullable AClassdef
103 # is `no_contract` annotation was found
104 var find_no_contract
= false
106 # The reference to the `in_contract` attribute.
107 # This attribute is used to disable contract verification when you are already in a contract verification.
108 # Keep the `in_contract` attribute to avoid searching at each contrat
109 var in_contract_attribute
: nullable MAttribute = null
111 redef fun visit
(node
)
113 node
.create_contracts
(self)
117 # Method use to define the signature part of the method `ASignature` and `MSignature`
118 # The given `mcontract` provided `adapt_nsignature` and `adapt_msignature` to copy and adapt the given signature (`nsignature` and `msignature`)
119 fun define_signature
(mcontract
: MContract, nsignature
: nullable ASignature, msignature
: nullable MSignature)
121 if nsignature
!= null and msignature
!= null then nsignature
.ret_type
= msignature
.return_mtype
122 self.n_signature
= mcontract
.adapt_nsignature
(nsignature
)
123 self.m_signature
= mcontract
.adapt_msignature
(msignature
)
126 # Define the new contract take in consideration that an old contract exist or not
127 private fun build_contract
(n_annotations
: Array[AAnnotation], mcontract
: MContract, mclassdef
: MClassDef)
129 var n_conditions
= new Array[AExpr]
130 # Retrieving the expression provided in the annotation
131 for n_annotation
in n_annotations
do n_conditions
.add n_annotation
.construct_condition
(self)
132 if is_intro_contract
then
133 # Create new contract method
134 mcontract
.create_intro_contract
(self, n_conditions
, mclassdef
)
136 # Create a redef of contract to take in consideration the new condition
137 mcontract
.create_subcontract
(self, n_conditions
, mclassdef
)
141 # Inject the incontract attribute (`_in_contract_`) in the `Sys` class
142 # This attribute allows to check if the contract need to be executed
143 private fun inject_incontract_in_sys
145 # If the `in_contract_attribute` already know just return
146 if in_contract_attribute
!= null then return
148 var sys_class
= toolcontext
.modelbuilder
.get_mclass_by_name
(visited_module
, mainmodule
, "Sys")
150 # Try to get the `in_contract` property, if it has already defined in a previously visited module.
151 var in_contract_property
= toolcontext
.modelbuilder
.try_get_mproperty_by_name
(visited_module
, sys_class
.intro
, "__in_contract_")
152 if in_contract_property
!= null then
153 self.in_contract_attribute
= in_contract_property
.as(MAttribute)
157 var bool_false
= new AFalseExpr.make
(mainmodule
.bool_type
)
158 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
)
160 in_contract_attribute
= n_in_contract_attribute
.mpropdef
.mproperty
163 # Return the `_in_contract_` attribute.
164 # If the attribute `_in_contract_` does not exist it's injected with `inject_incontract_in_sys`
165 private fun get_incontract
: MAttribute
167 if self.in_contract_attribute
== null then inject_incontract_in_sys
168 return in_contract_attribute
.as(not null)
171 # Return an `AIfExpr` with the contract encapsulated by an `if` to check if it's already in a contract verification.
176 # fun bar([...]) is except([...])
178 # fun _contract_bar([...])
180 # if not sys._in_contract_ then
181 # sys._in_contract_ = true
183 # sys._in_contract_ = false
188 # fun _bar_expect([...])
192 private fun encapsulated_contract_call
(visited_method
: AMethPropdef, call_to_contracts
: Array[ACallExpr]): AIfExpr
194 var sys_property
= toolcontext
.modelbuilder
.model
.get_mproperties_by_name
("sys").first
.as(MMethod)
195 var callsite_sys
= ast_builder
.create_callsite
(toolcontext
.modelbuilder
, visited_method
, sys_property
, true)
197 var incontract_attribute
= get_incontract
199 var callsite_get_incontract
= ast_builder
.create_callsite
(toolcontext
.modelbuilder
, visited_method
, incontract_attribute
.getter
.as(MMethod), false)
200 var callsite_set_incontract
= ast_builder
.create_callsite
(toolcontext
.modelbuilder
, visited_method
, incontract_attribute
.setter
.as(MMethod), false)
202 var n_condition
= ast_builder
.make_not
(ast_builder
.make_call
(ast_builder
.make_call
(new ASelfExpr, callsite_sys
, null), callsite_get_incontract
, null))
204 var n_if
= ast_builder
.make_if
(n_condition
, null)
206 var if_then_block
= n_if
.n_then
.as(ABlockExpr)
208 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
)]))
209 if_then_block
.add_all
(call_to_contracts
)
210 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
)]))
216 # This visitor checks the `callsite` to see if the target `mpropdef` has a contract.
217 private class CallSiteVisitor
220 # Instance of the toolcontext
221 var toolcontext
: ToolContext
223 var ast_builder
: ASTBuilder
225 # Actual visited method
226 var visited_propdef
: APropdef is noinit
228 redef fun visit
(node
)
230 node
.check_callsite
(self)
234 # Check if the callsite is bound on a property with a contract.
235 # If the property is linked to a contract a new callsite will be created towards the correct facet,
236 # in the other case the returned callsite wall be the same as the given `callsite`
237 private fun drive_callsite_to_contract
(callsite
: CallSite): CallSite
239 var contract_facet
= callsite
.mproperty
.mcontract_facet
240 var visited_mpropdef
= visited_propdef
.mpropdef
242 if visited_mpropdef
isa MContract or visited_mpropdef
isa MFacet then return callsite
243 if visited_mpropdef
== null or contract_facet
== null then return callsite
245 return ast_builder
.create_callsite
(toolcontext
.modelbuilder
, visited_propdef
, contract_facet
, callsite
.recv_is_self
)
250 private fun create_contracts
(v
: ContractsVisitor) do end
251 private fun check_callsite
(v
: CallSiteVisitor) do end
254 redef class AAnnotation
256 # Returns the conditions of annotation parameters. If there are several parameters, the result is an `AAndExpr`
258 # the contract `ensure(true, i == 10, f >= 1.0)`
259 # return this condition `(true and i == 10 and f >= 1.0)`
260 private fun construct_condition
(v
: ContractsVisitor): AExpr
262 var n_condition
= n_args
.first
264 for n_arg
in n_args
do n_condition
= v
.ast_builder
.make_and
(n_condition
, n_arg
)
265 n_condition
.location
= self.location
270 redef class MContract
272 # Should contract be called?
273 # return `true` if the contract needs to be called.
274 private fun is_called
(v
: ContractsVisitor, mpropdef
: MPropDef): Bool
276 return v
.toolcontext
.opt_full_contract
.value
279 # Method use to diplay warning when the contract is not present at the introduction
280 private fun no_intro_contract
(v
: ContractsVisitor, a
: Array[AAnnotation])do end
282 # Creating specific inheritance block contract
284 # `super_args` : Correspond to the `super` call arguments
285 private fun create_inherit_nblock
(v
: ContractsVisitor, n_conditions
: Array[AExpr], super_args
: Array[AExpr]): ABlockExpr is abstract
287 # Method to adapt the given `n_mpropdef.n_block` to the contract
288 private fun adapt_method_to_contract
(v
: ContractsVisitor, mfacet
: MFacet, n_mpropdef
: AMethPropdef) is abstract
290 # Create and return an adapted `MSignature` specifically for the contract in fonction of the given `m_signature`
291 private fun adapt_specific_msignature
(m_signature
: MSignature): MSignature do return m_signature
.adapt_to_contract
293 # Create and return an adapted `ASignature` specifically for the contract in fonction of the given `n_signature`
294 private fun adapt_specific_nsignature
(n_signature
: ASignature): ASignature do return n_signature
.adapt_to_contract
296 # Adapt the `m_signature` to the contract
297 # If `m_signature == null` return a new `MSignature` else it calls a specific adapt method see `adapt_specific_msignature`
298 private fun adapt_msignature
(m_signature
: nullable MSignature): MSignature
300 if m_signature
== null then return new MSignature(new Array[MParameter])
301 return adapt_specific_msignature
(m_signature
)
304 # Adapt the `n_signature` to the contract
305 # If `n_signature == null` return a new `ASignature` else it calls a specific adapt method see `adapt_specific_nsignature`
306 private fun adapt_nsignature
(n_signature
: nullable ASignature): ASignature
308 if n_signature
== null then return new ASignature
309 return adapt_specific_nsignature
(n_signature
)
312 # Create the initial contract (intro)
313 # All contracts have the same implementation for the introduction.
319 # assert contract_condition
323 private fun create_intro_contract
(v
: ContractsVisitor, n_conditions
: Array[AExpr], mclassdef
: MClassDef)
325 var n_block
= v
.ast_builder
.make_block
326 for n_condition
in n_conditions
do
327 # Create a new tid to set the name of the assert for more explicit error
328 var tid
= new TId.init_tk
(n_condition
.location
)
329 tid
.text
= "{n_condition.location.text}"
330 var n_assert
= v
.ast_builder
.make_assert
(tid
, n_condition
, null)
331 # Define the assert location to reference the annoation
332 n_assert
.location
= n_condition
.location
335 make_contract
(v
, n_block
, mclassdef
)
338 # Create a contract to check the old (super call) and the new conditions
344 # super # Call the old contracts
345 # assert new_condition
349 private fun create_subcontract
(v
: ContractsVisitor, n_conditions
: Array[AExpr], mclassdef
: MClassDef)
351 var args
= v
.n_signature
.make_parameter_read
(v
.ast_builder
)
352 var n_block
= v
.ast_builder
.make_block
353 n_block
= self.create_inherit_nblock
(v
, n_conditions
, args
)
354 make_contract
(v
, n_block
, mclassdef
)
357 # Build a new contract method with a specific block `n_block` in a specified `mclassdef`
358 private fun make_contract
(v
: ContractsVisitor, n_block
: AExpr, mclassdef
: MClassDef)
360 var n_contractdef
= v
.toolcontext
.modelbuilder
.create_method_from_property
(self, mclassdef
, false, v
.m_signature
)
361 # Set the signature to keep the same variable
362 n_contractdef
.n_signature
= v
.n_signature
363 n_contractdef
.n_block
= n_block
364 # Define the location of the new method for corresponding of the annotation location
365 n_contractdef
.location
= v
.current_location
366 n_contractdef
.do_all
(v
.toolcontext
)
372 redef fun is_called
(v
: ContractsVisitor, mpropdef
: MPropDef): Bool
374 var main_package
= v
.mainmodule
.mpackage
375 var actual_package
= mpropdef
.mclassdef
.mmodule
.mpackage
376 if main_package
!= null and actual_package
!= null then
377 var condition_direct_arc
= v
.toolcontext
.modelbuilder
.model
.mpackage_importation_graph
.has_arc
(main_package
, actual_package
)
378 return super or main_package
== actual_package
or condition_direct_arc
383 # Display warning if no contract is defined at introduction `expect`,
384 # because if no contract is defined at the introduction the added
385 # contracts will not cause any error even if they are not satisfied.
391 # fun _bar_expect([...])
398 # redef fun bar is expect(contract_condition)
399 # redef fun _bar_expect([...])
401 # if (contract_condition) then return
407 redef fun no_intro_contract
(v
: ContractsVisitor, a
: Array[AAnnotation])
409 v
.toolcontext
.warning
(a
.first
.location
,"useless_contract","Useless contract: No contract defined at the introduction of the method")
412 redef fun create_inherit_nblock
(v
: ContractsVisitor, n_conditions
: Array[AExpr], super_args
: Array[AExpr]): ABlockExpr
414 var n_block
= v
.ast_builder
.make_block
415 for n_condition
in n_conditions
do
416 # Creating the if expression with the new condition
417 var if_block
= v
.ast_builder
.make_if
(n_condition
, n_condition
.mtype
)
418 # Creating and adding return expr to the then case
419 if_block
.n_then
= v
.ast_builder
.make_return
420 if_block
.location
= n_condition
.location
423 n_block
.add v
.ast_builder
.make_super_call
(super_args
)
427 redef fun adapt_method_to_contract
(v
: ContractsVisitor, mfacet
: MFacet, n_mpropdef
: AMethPropdef)
429 var callsite
= v
.ast_builder
.create_callsite
(v
.toolcontext
.modelbuilder
, n_mpropdef
, self, true)
430 var args
= n_mpropdef
.n_signature
.make_parameter_read
(v
.ast_builder
)
431 var n_callexpect
= v
.ast_builder
.make_call
(new ASelfExpr, callsite
,args
)
432 # Creation of the new instruction block with the call to expect condition
433 var actual_expr
= n_mpropdef
.n_block
434 var new_block
= new ABlockExpr
435 new_block
.n_expr
.push v
.encapsulated_contract_call
(n_mpropdef
, [n_callexpect
])
436 if actual_expr
isa ABlockExpr then
437 new_block
.n_expr
.add_all
(actual_expr
.n_expr
)
438 else if actual_expr
!= null then
439 new_block
.n_expr
.push
(actual_expr
)
441 n_mpropdef
.n_block
= new_block
442 mfacet
.has_applied_expect
= true
446 redef class BottomMContract
448 redef fun is_called
(v
: ContractsVisitor, mpropdef
: MPropDef): Bool
450 return super or v
.mainmodule
.mpackage
== mpropdef
.mclassdef
.mmodule
.mpackage
453 redef fun create_inherit_nblock
(v
: ContractsVisitor, n_conditions
: Array[AExpr], super_args
: Array[AExpr]): ABlockExpr
455 # Define contract block
456 var n_block
= v
.ast_builder
.make_block
458 var super_call
= v
.ast_builder
.make_super_call
(super_args
)
460 n_block
.add super_call
461 for n_condition
in n_conditions
do
462 var tid
= new TId.init_tk
(n_condition
.location
)
463 tid
.text
= "{n_condition.location.text}"
464 # Creating the assert expression with the new condition
465 var n_assert
= v
.ast_builder
.make_assert
(tid
, n_condition
)
466 n_assert
.location
= n_condition
.location
472 # Inject the `result` variable into the `n_block` of the given n_mpropdef`.
474 # The purpose of the variable is to capture return values to use it in contracts.
475 private fun inject_result
(v
: ContractsVisitor, n_mpropdef
: AMethPropdef, ret_type
: MType): Variable
477 expect
(n_mpropdef
.n_signature
.n_type
!= null)
479 var actual_block
= n_mpropdef
.n_block
480 # never happen. If it's the case the problem is in the contract facet building
481 assert actual_block
isa ABlockExpr
483 var return_expr
= actual_block
.n_expr
.last
.as(AReturnExpr)
485 var returned_expr
= return_expr
.n_expr
486 # The return node has no returned expression
487 assert returned_expr
!= null
489 var return_var
= new Variable("result")
490 # Creating a new variable to keep the old return of the method
491 var assign_result
= v
.ast_builder
.make_var_assign
(return_var
, returned_expr
)
492 # Remove the actual return
493 actual_block
.n_expr
.pop
494 # Set the return type
495 return_var
.declared_type
= ret_type
496 # Adding the reading expr of result variable to the block
497 actual_block
.add assign_result
498 # Expr to read the result variable
499 var read_result
= v
.ast_builder
.make_var_read
(return_var
, ret_type
)
500 # Definition of the new return
501 return_expr
= v
.ast_builder
.make_return
(read_result
)
502 actual_block
.add return_expr
509 redef fun adapt_specific_msignature
(m_signature
: MSignature): MSignature
511 return m_signature
.adapt_to_ensurecondition
514 redef fun adapt_specific_nsignature
(n_signature
: ASignature): ASignature
516 return n_signature
.adapt_to_ensurecondition
519 redef fun adapt_method_to_contract
(v
: ContractsVisitor, mfacet
: MFacet, n_mpropdef
: AMethPropdef)
521 var callsite
= v
.ast_builder
.create_callsite
(v
.toolcontext
.modelbuilder
, n_mpropdef
, self, true)
522 var n_self
= new ASelfExpr
523 # argument to call the contract method
524 var args
= n_mpropdef
.n_signature
.make_parameter_read
(v
.ast_builder
)
526 var actual_block
= n_mpropdef
.n_block
527 # never happen. If it's the case the problem is in the contract facet building
528 assert actual_block
isa ABlockExpr
530 var ret_type
= n_mpropdef
.mpropdef
.mproperty
.intro
.msignature
.return_mtype
531 if ret_type
!= null then
532 # Inject the variable result
533 var result_var
= inject_result
(v
, n_mpropdef
, ret_type
)
534 # Expr to read the result variable
535 var read_result
= v
.ast_builder
.make_var_read
(result_var
, ret_type
)
536 var return_expr
= actual_block
.n_expr
.pop
537 # Adding the read return to argument
538 args
.add
(read_result
)
539 var n_call_contract
= v
.ast_builder
.make_call
(n_self
, callsite
, args
)
540 actual_block
.add_all
([v
.encapsulated_contract_call
(n_mpropdef
, [n_call_contract
]), return_expr
])
542 # build the call to the contract method
543 var n_call_contract
= v
.ast_builder
.make_call
(n_self
, callsite
, args
)
544 actual_block
.add v
.encapsulated_contract_call
(n_mpropdef
, [n_call_contract
])
546 n_mpropdef
.do_all
(v
.toolcontext
)
547 mfacet
.has_applied_ensure
= true
553 # Define contract facet for MMethod in the given mclassdef. The facet represents the entry point with contracts (expect, ensure) of the method.
554 # If a contract is given adapt the contract facet.
556 # `classdef`: Indicates the class where we want to introduce our facet
557 # `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.
564 # fun add_one is ensure(old(i) + 1 == i)
568 # fun add_one is ensure(old(i) + 1 == i)
570 # # The contract facet
571 # fun contract_add_one do
573 # ensure_add_one(old_add_one)
577 private fun define_contract_facet
(v
: ContractsVisitor, classdef
: MClassDef, mcontract
: nullable MContract)
579 var exist_contract_facet
= has_contract_facet
580 var contract_facet
= build_contract_facet
581 # Do nothing the contract and the contract facet already exist
582 if mcontract
!= null and mcontract
.is_already_applied
(contract_facet
) then return
584 var n_contract_facet
: AMethPropdef
585 if not exist_contract_facet
then
586 # If has no contract facet in intro just create it
587 if classdef
!= intro_mclassdef
then
588 create_facet
(v
, intro_mclassdef
, contract_facet
, self)
590 n_contract_facet
= create_facet
(v
, classdef
, contract_facet
, self)
592 # Check if the contract facet already exist in this context (in this classdef)
593 if classdef
.mpropdefs_by_property
.has_key
(contract_facet
) then
595 n_contract_facet
= v
.toolcontext
.modelbuilder
.mpropdef2node
(classdef
.mpropdefs_by_property
[contract_facet
]).as(AMethPropdef)
597 # create a new contract facet definition
598 n_contract_facet
= create_facet
(v
, classdef
, contract_facet
, self)
599 var block
= v
.ast_builder
.make_block
600 # super call to the contract facet
601 var args
= n_contract_facet
.n_signature
.make_parameter_read
(v
.ast_builder
)
602 var n_super_call
= v
.ast_builder
.make_super_call
(args
)
603 # verification for add a return or not
604 if self.intro
.msignature
.return_mtype
!= null then
605 block
.add
(v
.ast_builder
.make_return
(n_super_call
))
607 block
.add
(n_super_call
)
609 n_contract_facet
.n_block
= block
612 if mcontract
!= null then mcontract
.adapt_method_to_contract
(v
, contract_facet
, n_contract_facet
)
614 n_contract_facet
.location
= v
.current_location
615 n_contract_facet
.do_all
(v
.toolcontext
)
618 # Method to create a facet of the method.
619 # See `define_contract_facet` for more information about two types of facets.
621 # `called` : is the property to call in this facet.
622 private fun create_facet
(v
: ContractsVisitor, classdef
: MClassDef, facet
: MFacet, called
: MMethod): AMethPropdef
624 expect
( called
.is_same_instance
(self) or called
.is_same_instance
(self.mcontract_facet
) )
626 # Defines the contract facet is an init or not
627 # it is necessary to use the contracts with in a constructor
628 facet
.is_init
= is_init
629 var n_contractdef
= v
.toolcontext
.modelbuilder
.create_method_from_property
(facet
, classdef
, false, self.intro
.msignature
)
630 # FIXME set the location because the callsite creation need the node location
631 n_contractdef
.location
= v
.current_location
632 n_contractdef
.validate
634 var block
= v
.ast_builder
.make_block
636 # Arguments to call the `called` property
637 var args
: Array[AExpr]
638 args
= n_contractdef
.n_signature
.make_parameter_read
(v
.ast_builder
)
640 var callsite
= v
.ast_builder
.create_callsite
(v
.toolcontext
.modelbuilder
, n_contractdef
, called
, true)
641 var n_call
= v
.ast_builder
.make_call
(new ASelfExpr, callsite
, args
)
643 if self.intro
.msignature
.return_mtype
== null then
646 block
.add
(v
.ast_builder
.make_return
(n_call
))
649 n_contractdef
.n_block
= block
650 n_contractdef
.do_all
(v
.toolcontext
)
655 redef class MMethodDef
657 # Entry point to build contract (define the contract facet and define the contract method verification)
658 private fun construct_contract
(v
: ContractsVisitor, n_signature
: ASignature, n_annotations
: Array[AAnnotation], mcontract
: MContract, exist_contract
: Bool)
660 v
.define_signature
(mcontract
, n_signature
, mproperty
.intro
.msignature
)
661 if not exist_contract
and not is_intro
then no_intro_contract
(v
, mcontract
, n_annotations
)
662 v
.build_contract
(n_annotations
, mcontract
, mclassdef
)
663 # Check if the contract must be called to know if it's needed to construct the facet
664 if mcontract
.is_called
(v
, self) then mproperty
.define_contract_facet
(v
, mclassdef
, mcontract
)
667 # Create a contract on the introduction classdef of the property.
668 # Display an warning message if necessary
669 private fun no_intro_contract
(v
: ContractsVisitor, mcontract
: MContract, n_annotations
: Array[AAnnotation])
671 v
.toolcontext
.modelbuilder
.create_method_from_property
(mcontract
, mcontract
.intro_mclassdef
, false, v
.m_signature
)
672 mcontract
.no_intro_contract
(v
, n_annotations
)
675 # Apply the `no_contract` annotation to the contract. This method removes the inheritance by adding an empty contract method.
676 # Display a warning if the annotation is not needed
677 private fun no_contract_apply
(v
: ContractsVisitor, n_signature
: ASignature)
679 var mensure
= mproperty
.mensure
680 var mexpect
= mproperty
.mexpect
681 if mensure
== null and mexpect
== null then
682 v
.toolcontext
.warning
(location
, "useless_nocontract", "Useless `no_contract`, no contract was declared for `{name}`. Remove the `no_contract`")
684 if mensure
!= null then
685 # Add an empty ensure method to replace the actual definition
686 v
.toolcontext
.modelbuilder
.create_method_from_property
(mensure
, mclassdef
, false, mensure
.intro
.msignature
)
688 if mexpect
!= null then
689 # Add an empty expect method to replace the actual definition
690 v
.toolcontext
.modelbuilder
.create_method_from_property
(mexpect
, mclassdef
, false, mexpect
.intro
.msignature
)
696 redef fun check_callsite
(v
)
698 v
.visited_propdef
= self
702 redef class AMethPropdef
704 # Entry point to create a contract (verification of inheritance, or new contract).
705 redef fun create_contracts
(v
)
707 v
.ast_builder
.check_mmodule
(mpropdef
.mclassdef
.mmodule
)
708 v
.current_location
= self.location
709 v
.is_intro_contract
= mpropdef
.is_intro
711 if not mpropdef
.is_intro
then check_redef
(v
)
714 # Verification of the annotation to know if it is a contract annotation.
715 # If this is the case, we built the appropriate contract.
716 private fun check_annotation
(v
: ContractsVisitor)
718 var annotations_expect
= get_annotations
("expect")
719 var annotations_ensure
= get_annotations
("ensure")
720 var annotation_no_contract
= get_annotations
("no_contract")
722 if (not annotations_expect
.is_empty
or not annotations_ensure
.is_empty
) and not annotation_no_contract
.is_empty
then
723 v
.toolcontext
.error
(location
, "The new contract definition is not correct when using `no_contract`. Remove the contract definition or the `no_contract`")
727 var nsignature
= n_signature
or else new ASignature
729 if not annotation_no_contract
.is_empty
then
730 mpropdef
.no_contract_apply
(v
, nsignature
.clone
)
734 if not annotations_expect
.is_empty
then
735 var exist_contract
= mpropdef
.mproperty
.has_expect
736 mpropdef
.construct_contract
(v
, nsignature
.clone
, annotations_expect
, mpropdef
.mproperty
.build_expect
, exist_contract
)
739 if not annotations_ensure
.is_empty
then
740 var exist_contract
= mpropdef
.mproperty
.has_ensure
741 mpropdef
.construct_contract
(v
, nsignature
.clone
, annotations_ensure
, mpropdef
.mproperty
.build_ensure
, exist_contract
)
745 # Verification if the method have an inherited contract to apply it.
746 private fun check_redef
(v
: ContractsVisitor)
748 var mexpect
= mpropdef
.mproperty
.mexpect
749 var mensure
= mpropdef
.mproperty
.mensure
750 var mcontract_facet
= mpropdef
.mproperty
.mcontract_facet
752 if mexpect
!= null then
753 if mcontract_facet
!= null and mcontract_facet
.has_applied_expect
then return
754 if mexpect
.is_called
(v
, mpropdef
.as(not null)) then mpropdef
.mproperty
.define_contract_facet
(v
, mpropdef
.mclassdef
, mexpect
)
756 if mensure
!= null then
757 if mcontract_facet
!= null and mcontract_facet
.has_applied_ensure
then return
758 if mensure
.is_called
(v
, mpropdef
.as(not null)) then mpropdef
.mproperty
.define_contract_facet
(v
, mpropdef
.mclassdef
, mensure
)
763 redef class MSignature
765 # Adapt signature for an contract
767 # The returned `MSignature` is the copy of `self` without return type.
768 private fun adapt_to_contract
: MSignature do return new MSignature(mparameters
.to_a
, null)
770 # Adapt signature for a ensure contract
772 # The returned `MSignature` is the copy of `self` without return type.
773 # The return type is replaced by a new parameter `result`
774 private fun adapt_to_ensurecondition
: MSignature
776 var rtype
= return_mtype
777 var msignature
= adapt_to_contract
778 if rtype
!= null then
779 msignature
.mparameters
.add
(new MParameter("result", rtype
, false))
784 # The returned `MSignature` is the exact copy of `self`.
785 private fun clone
: MSignature do return new MSignature(mparameters
.to_a
, return_mtype
)
788 redef class ASignature
790 # Create an array of AVarExpr representing the read of every parameters
791 private fun make_parameter_read
(ast_builder
: ASTBuilder): Array[AVarExpr]
793 var args
= new Array[AVarExpr]
794 for n_param
in self.n_params
do
795 var mtype
= n_param
.variable
.declared_type
796 var variable
= n_param
.variable
797 if variable
!= null and mtype
!= null then
798 args
.push ast_builder
.make_var_read
(variable
, mtype
)
804 # Create a new ASignature adapted for contract
806 # The returned `ASignature` is the copy of `self` without return type.
807 private fun adapt_to_contract
: ASignature
809 var adapt_nsignature
= self.clone
810 if adapt_nsignature
.n_type
!= null then adapt_nsignature
.n_type
.detach
811 return adapt_nsignature
814 # Create a new ASignature adapted for ensure
816 # The returned `ASignature` is the copy of `self` without return type.
817 # The return type is replaced by a new parameter `result`
818 private fun adapt_to_ensurecondition
: ASignature do
819 var nsignature
= adapt_to_contract
820 if ret_type
!= null then
821 var variable
= new Variable("result")
822 variable
.declared_type
= ret_type
823 nsignature
.n_params
.add
new AParam.make
(variable
, ret_type
.create_ast_representation
)
829 redef class ASendExpr
830 redef fun check_callsite
(v
)
832 var actual_callsite
= callsite
833 if actual_callsite
!= null then
834 callsite
= v
.drive_callsite_to_contract
(actual_callsite
)
840 redef fun check_callsite
(v
)
842 var actual_callsite
= callsite
843 if actual_callsite
!= null then
844 callsite
= v
.drive_callsite_to_contract
(actual_callsite
)