model/model_contract: Move contract model representation
[nit.git] / src / contracts.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
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.
17 #
18 # FIXME Split the module in three parts: extension of the modele, building phase and the "re-driving"
19 module contracts
20
21 import parse_annotations
22 import phase
23 import semantize
24 intrude import model_contract
25 intrude import astbuilder
26 intrude import modelize_property
27 intrude import scope
28 intrude import typing
29
30 redef class ToolContext
31 # Parses contracts annotations.
32 var contracts_phase: Phase = new ContractsPhase(self, [modelize_property_phase,typing_phase])
33 end
34
35 private class ContractsPhase
36 super Phase
37
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)
45 end
46 end
47
48 redef class AModule
49 # Compile all contracts
50 #
51 # The implementation of the contracts is done in two visits.
52 #
53 # - First step, the visitor analyzes and constructs the contracts
54 # for each class (invariant) and method (expect, ensure).
55 #
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 #
61 var contract_visitor = new ContractsVisitor(toolcontext, toolcontext.modelbuilder.identified_modules.first, self, new ASTBuilder(mmodule.as(not null)))
62 contract_visitor.enter_visit(self)
63 #
64 var callsite_visitor = new CallSiteVisitor(toolcontext)
65 callsite_visitor.enter_visit(self)
66 end
67 end
68
69 # Visitor to build all contracts.
70 private class ContractsVisitor
71 super Visitor
72
73 # Instance of the toolcontext
74 var toolcontext: ToolContext
75
76 # The main module
77 # It's strore to define if the contract need to be build depending on the selected policy `--no-contract` `--full-contract` or default
78 var mainmodule: MModule
79
80 # Actual visited module
81 var visited_module: AModule
82
83 var ast_builder: ASTBuilder
84
85 # The `ASignature` of the actual build contract
86 var n_signature: ASignature is noinit
87
88 # The `MSignature` of the actual build contract
89 var m_signature: MSignature is noinit
90
91 # The `current_location` can corresponding of the annotation or method location.
92 var current_location: Location is noinit
93
94 # Is the contrat is an introduction or not?
95 # This attribute has the same value as the `is_intro` of the propdef attached to the contract.
96 # 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.
97 var is_intro_contract: Bool is noinit
98
99 # Actual visited class
100 var visited_class: nullable AClassdef
101
102 # is `no_contract` annotation was found
103 var find_no_contract = false
104
105 # The reference to the `in_contract` attribute.
106 # This attribute is used to disable contract verification when you are already in a contract verification.
107 # Keep the `in_contract` attribute to avoid searching at each contrat
108 var in_contract_attribute: nullable MAttribute = null
109
110 redef fun visit(node)
111 do
112 node.create_contracts(self)
113 node.visit_all(self)
114 end
115
116 # Method use to define the signature part of the method `ASignature` and `MSignature`
117 # The given `mcontract` provided `adapt_nsignature` and `adapt_msignature` to copy and adapt the given signature (`nsignature` and `msignature`)
118 fun define_signature(mcontract: MContract, nsignature: nullable ASignature, msignature: nullable MSignature)
119 do
120 if nsignature != null and msignature != null then nsignature.ret_type = msignature.return_mtype
121 self.n_signature = mcontract.adapt_nsignature(nsignature)
122 self.m_signature = mcontract.adapt_msignature(msignature)
123 end
124
125 # Define the new contract take in consideration that an old contract exist or not
126 private fun build_contract(n_annotation: AAnnotation, mcontract: MContract, mclassdef: MClassDef): MMethodDef
127 do
128 self.current_location = n_annotation.location
129 # Retrieving the expression provided in the annotation
130 var n_condition = n_annotation.construct_condition(self)
131 var m_contractdef: AMethPropdef
132 if is_intro_contract then
133 # Create new contract method
134 m_contractdef = mcontract.create_intro_contract(self, n_condition, mclassdef)
135 else
136 # Create a redef of contract to take in consideration the new condition
137 m_contractdef = mcontract.create_subcontract(self, n_condition, mclassdef)
138 end
139 var contract_propdef = m_contractdef.mpropdef
140 # The contract has a null mpropdef, this should never happen
141 assert contract_propdef != null
142 return contract_propdef
143 end
144
145 # Verification if the construction of the contract is necessary.
146 # Three cases are checked for `expect`:
147 #
148 # - Was the `--full-contract` option passed?
149 # - Is the method is in the main package?
150 # - Is the method is in a direct imported package?
151 #
152 fun check_usage_expect(actual_mmodule: MModule): Bool
153 do
154 var main_package = mainmodule.mpackage
155 var actual_package = actual_mmodule.mpackage
156 if main_package != null and actual_package != null then
157 var condition_direct_arc = toolcontext.modelbuilder.model.mpackage_importation_graph.has_arc(main_package, actual_package)
158 return toolcontext.opt_full_contract.value or condition_direct_arc or main_package == actual_package
159 end
160 return false
161 end
162
163 # Verification if the construction of the contract is necessary.
164 # Two cases are checked for `ensure`:
165 #
166 # - Was the `--full-contract` option passed?
167 # - Is the method is in the main package?
168 #
169 fun check_usage_ensure(actual_mmodule: MModule): Bool
170 do
171 return toolcontext.opt_full_contract.value or mainmodule.mpackage == actual_mmodule.mpackage
172 end
173
174 # Inject the incontract attribute (`_in_contract_`) in the `Sys` class
175 # This attribute allows to check if the contract need to be executed
176 private fun inject_incontract_in_sys
177 do
178 # If the `in_contract_attribute` already know just return
179 if in_contract_attribute != null then return
180
181 var sys_class = toolcontext.modelbuilder.get_mclass_by_name(visited_module, mainmodule, "Sys")
182
183 # Try to get the `in_contract` property, if it has already defined in a previously visited module.
184 var in_contract_property = toolcontext.modelbuilder.try_get_mproperty_by_name(visited_module, sys_class.intro, "__in_contract_")
185 if in_contract_property != null then
186 self.in_contract_attribute = in_contract_property.as(MAttribute)
187 return
188 end
189
190 var bool_false = new AFalseExpr.make(mainmodule.bool_type)
191 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)
192
193 in_contract_attribute = n_in_contract_attribute.mpropdef.mproperty
194 end
195
196 # Return the `_in_contract_` attribute.
197 # If the attribute `_in_contract_` does not exist it's injected with `inject_incontract_in_sys`
198 private fun get_incontract: MAttribute
199 do
200 if self.in_contract_attribute == null then inject_incontract_in_sys
201 return in_contract_attribute.as(not null)
202 end
203
204 # Return an `AIfExpr` with the contract encapsulated by an `if` to check if it's already in a contract verification.
205 #
206 # Example:
207 # ~~~nitish
208 # class A
209 # fun bar([...]) is except([...])
210 #
211 # fun _contract_bar([...])
212 # do
213 # if not sys._in_contract_ then
214 # sys._in_contract_ = true
215 # _bar_expect([...])
216 # sys._in_contract_ = false
217 # end
218 # bar([...])
219 # end
220 #
221 # fun _bar_expect([...])
222 # end
223 # ~~~~
224 #
225 private fun encapsulated_contract_call(visited_method: AMethPropdef, call_to_contracts: Array[ACallExpr]): AIfExpr
226 do
227 var sys_property = toolcontext.modelbuilder.model.get_mproperties_by_name("sys").first.as(MMethod)
228 var callsite_sys = ast_builder.create_callsite(toolcontext.modelbuilder, visited_method, sys_property, true)
229
230 var incontract_attribute = get_incontract
231
232 var callsite_get_incontract = ast_builder.create_callsite(toolcontext.modelbuilder, visited_method, incontract_attribute.getter.as(MMethod), false)
233 var callsite_set_incontract = ast_builder.create_callsite(toolcontext.modelbuilder, visited_method, incontract_attribute.setter.as(MMethod), false)
234
235 var n_condition = ast_builder.make_not(ast_builder.make_call(ast_builder.make_call(new ASelfExpr, callsite_sys, null), callsite_get_incontract, null))
236
237 var n_if = ast_builder.make_if(n_condition, null)
238
239 var if_then_block = n_if.n_then.as(ABlockExpr)
240
241 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)]))
242 if_then_block.add_all(call_to_contracts)
243 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)]))
244
245 return n_if
246 end
247 end
248
249 # This visitor checks the `callsite` to see if the target `mpropdef` has a contract.
250 private class CallSiteVisitor
251 super Visitor
252
253
254 # Instance of the toolcontext
255 var toolcontext: ToolContext
256
257 # Actual visited method
258 var visited_method: APropdef is noinit
259
260 redef fun visit(node)
261 do
262 node.check_callsite(self)
263 node.visit_all(self)
264 end
265
266 # Check if the callsite calls a method with a contract.
267 # If it's the case the callsite is replaced by another callsite to the contract method.
268 private fun drive_method_contract(callsite: CallSite): CallSite
269 do
270 if callsite.mproperty.mcontract_facet != null then
271 var contract_facet = callsite.mproperty.mcontract_facet
272 var visited_mpropdef = visited_method.mpropdef
273 assert contract_facet != null and visited_mpropdef != null
274
275 var type_visitor = new TypeVisitor(toolcontext.modelbuilder, visited_mpropdef)
276 var drived_callsite = type_visitor.build_callsite_by_property(visited_method, callsite.recv, contract_facet, callsite.recv_is_self)
277 # This never happen this check is here for debug
278 assert drived_callsite != null
279 return drived_callsite
280 end
281 return callsite
282 end
283 end
284
285 redef class ANode
286 private fun create_contracts(v: ContractsVisitor) do end
287 private fun check_callsite(v: CallSiteVisitor) do end
288 end
289
290 redef class AAnnotation
291
292 # Returns the conditions of annotation parameters. If there are several parameters, the result is an `AAndExpr`
293 # Example:
294 # the contract `ensure(true, i == 10, f >= 1.0)`
295 # return this condition `(true and i == 10 and f >= 1.0)`
296 private fun construct_condition(v : ContractsVisitor): AExpr
297 do
298 var n_condition = n_args.first
299 n_args.remove_at(0)
300 for n_arg in n_args do n_condition = v.ast_builder.make_and(n_condition, n_arg)
301 return n_condition
302 end
303 end
304
305 redef class MContract
306
307 # Define the name of the contract
308 fun contract_name: String is abstract
309
310 # Method use to diplay warning when the contract is not present at the introduction
311 private fun no_intro_contract(v: ContractsVisitor, a: AAnnotation)do end
312
313 # Creating specific inheritance block contract
314 private fun create_nblock(v: ContractsVisitor, n_condition: AExpr, args: Array[AExpr]): ABlockExpr is abstract
315
316 # Method to adapt the `n_mpropdef.n_block` to the contract
317 private fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef) is abstract
318
319 # Adapt the msignature specifically for the contract method
320 private fun adapt_specific_msignature(m_signature: MSignature): MSignature do return m_signature.adapt_to_contract
321
322 # Adapt the nsignature specifically for the contract method
323 private fun adapt_specific_nsignature(n_signature: ASignature): ASignature do return n_signature.adapt_to_contract
324
325 # Adapt the `m_signature` to the contract
326 # If it is not null call the specific adapt `m_signature` for the contract
327 private fun adapt_msignature(m_signature: nullable MSignature): MSignature
328 do
329 if m_signature == null then return new MSignature(new Array[MParameter])
330 return adapt_specific_msignature(m_signature)
331 end
332
333 # Adapt the `n_signature` to the contract
334 # If it is not null call the specific adapt `n_signature` for the contract
335 private fun adapt_nsignature(n_signature: nullable ASignature): ASignature
336 do
337 if n_signature == null then return new ASignature
338 return adapt_specific_nsignature(n_signature)
339 end
340
341 # Create a new empty contract
342 private fun create_empty_contract(v: ContractsVisitor, mclassdef: MClassDef, msignature: nullable MSignature, n_signature: ASignature)
343 do
344 var n_contract_def = intro_mclassdef.mclass.create_empty_method(v, self, mclassdef, msignature, n_signature)
345 n_contract_def.do_all(v.toolcontext)
346 end
347
348 # Create the initial contract (intro)
349 # All contracts have the same implementation for the introduction.
350 #
351 # Example:
352 # ~~~nitish
353 # fun contrat([...])
354 # do
355 # assert contract_condition
356 # end
357 # ~~~
358 #
359 private fun create_intro_contract(v: ContractsVisitor, n_condition: nullable AExpr, mclassdef: MClassDef): AMethPropdef
360 do
361 var n_block = v.ast_builder.make_block
362 if n_condition != null then
363 # Create a new tid to set the name of the assert for more explicit error
364 var tid = new TId.init_tk(self.location)
365 tid.text = "{self.contract_name}"
366 n_block.add v.ast_builder.make_assert(tid, n_condition, null)
367 end
368 return make_contract(v, n_block, mclassdef)
369 end
370
371 # Create a contract with old (super) and the new conditions
372 private fun create_subcontract(v: ContractsVisitor, ncondition: nullable AExpr, mclassdef: MClassDef): AMethPropdef
373 do
374 var args = v.n_signature.make_parameter_read(v.ast_builder)
375 var n_block = v.ast_builder.make_block
376 if ncondition != null then n_block = self.create_nblock(v, ncondition, args)
377 return make_contract(v, n_block, mclassdef)
378 end
379
380 # Build a method with a specific block `n_block` in a specified `mclassdef`
381 private fun make_contract(v: ContractsVisitor, n_block: AExpr, mclassdef: MClassDef): AMethPropdef
382 do
383 var n_contractdef = intro_mclassdef.mclass.create_empty_method(v, self, mclassdef, v.m_signature, v.n_signature)
384 n_contractdef.n_block = n_block
385 # Define the location of the new method for corresponding of the annotation location
386 n_contractdef.location = v.current_location
387 n_contractdef.do_all(v.toolcontext)
388 return n_contractdef
389 end
390 end
391
392 redef class MExpect
393
394 # Define the name of the contract
395 redef fun contract_name: String do return "expect"
396
397 # Display warning if no contract is defined at introduction `expect`,
398 # because if no contract is defined at the introduction the added
399 # contracts will not cause any error even if they are not satisfied.
400 #
401 # Example:
402 # ~~~nitish
403 # class A
404 # fun bar [...]
405 # fun _bar_expect([...])
406 # do
407 # [empty contract]
408 # end
409 # end
410 #
411 # redef class A
412 # redef fun bar is expect(contract_condition)
413 # redef fun _bar_expect([...])
414 # do
415 # if (contract_condition) then return
416 # super
417 # end
418 # end
419 # ~~~~
420 #
421 redef fun no_intro_contract(v: ContractsVisitor, a: AAnnotation)
422 do
423 v.toolcontext.warning(a.location,"","Useless contract: No contract defined at the introduction of the method")
424 end
425
426 redef fun create_nblock(v: ContractsVisitor, n_condition: AExpr, args: Array[AExpr]): ABlockExpr
427 do
428 # Creating the if expression with the new condition
429 var if_block = v.ast_builder.make_if(n_condition, n_condition.mtype)
430 # Creating and adding return expr to the then case
431 if_block.n_then = v.ast_builder.make_return(null)
432 # Creating the super call to the contract and adding this to else case
433 if_block.n_else = v.ast_builder.make_super_call(args,null)
434 var n_block = v.ast_builder.make_block
435 n_block.add if_block
436 return n_block
437 end
438
439 redef fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef)
440 do
441 var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_mpropdef, self, true)
442 var n_self = new ASelfExpr
443 var args = n_mpropdef.n_signature.make_parameter_read(v.ast_builder)
444 var n_callexpect = v.ast_builder.make_call(n_self,callsite,args)
445 # Creation of the new instruction block with the call to expect condition
446 var actual_expr = n_mpropdef.n_block
447 var new_block = new ABlockExpr
448 new_block.n_expr.push n_callexpect
449 if actual_expr isa ABlockExpr then
450 new_block.n_expr.add_all(actual_expr.n_expr)
451 else if actual_expr != null then
452 new_block.n_expr.push(actual_expr)
453 end
454 n_mpropdef.n_block = new_block
455 end
456 end
457
458 redef class BottomMContract
459
460 redef fun create_nblock(v: ContractsVisitor, n_condition: AExpr, args: Array[AExpr]): ABlockExpr
461 do
462 var tid = new TId.init_tk(v.current_location)
463 tid.text = "{contract_name}"
464 # Creating the assert expression with the new condition
465 var assert_block = v.ast_builder.make_assert(tid,n_condition,null)
466 # Creating the super call to the contract
467 var super_call = v.ast_builder.make_super_call(args,null)
468 # Define contract block
469 var n_block = v.ast_builder.make_block
470 # Adding the expressions to the block
471 n_block.add super_call
472 n_block.add assert_block
473 return n_block
474 end
475
476 # Inject the `result` variable into the `n_block` of the given n_mpropdef`.
477 #
478 # The purpose of the variable is to capture return values to use it in contracts.
479 private fun inject_result(v: ContractsVisitor, n_mpropdef: AMethPropdef, ret_type: MType): Variable
480 do
481 var actual_block = n_mpropdef.n_block
482 # never happen. If it's the case the problem is in the contract facet building
483 assert actual_block isa ABlockExpr
484
485 var return_var: nullable Variable = null
486
487 var return_expr = actual_block.n_expr.last.as(AReturnExpr)
488
489 var returned_expr = return_expr.n_expr
490 # The return node has no returned expression
491 assert returned_expr != null
492
493 # Check if the result variable already exit
494 if returned_expr isa AVarExpr then
495 if returned_expr.variable.name == "result" then
496 return_var = returned_expr.variable
497 end
498 end
499
500 return_var = new Variable("result")
501 # Creating a new variable to keep the old return of the method
502 var assign_result = v.ast_builder.make_var_assign(return_var, returned_expr)
503 # Remove the actual return
504 actual_block.n_expr.pop
505 # Set the return type
506 return_var.declared_type = ret_type
507 # Adding the reading expr of result variable to the block
508 actual_block.add assign_result
509 # Expr to read the result variable
510 var read_result = v.ast_builder.make_var_read(return_var, ret_type)
511 # Definition of the new return
512 return_expr = v.ast_builder.make_return(read_result)
513 actual_block.add return_expr
514 return return_var
515 end
516 end
517
518 redef class MEnsure
519
520 # Define the name of the contract
521 redef fun contract_name: String do return "ensure"
522
523 redef fun adapt_specific_msignature(m_signature: MSignature): MSignature
524 do
525 return m_signature.adapt_to_ensurecondition
526 end
527
528 redef fun adapt_specific_nsignature(n_signature: ASignature): ASignature
529 do
530 return n_signature.adapt_to_ensurecondition
531 end
532
533 redef fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef)
534 do
535 var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_mpropdef, self, true)
536 var n_self = new ASelfExpr
537 # argument to call the contract method
538 var args = n_mpropdef.n_signature.make_parameter_read(v.ast_builder)
539 # Inject the variable result
540 # The cast is always safe because the online adapted method is the contract facet
541
542 var actual_block = n_mpropdef.n_block
543 # never happen. If it's the case the problem is in the contract facet building
544 assert actual_block isa ABlockExpr
545
546 var ret_type = n_mpropdef.mpropdef.mproperty.intro.msignature.return_mtype
547 if ret_type != null then
548 var result_var = inject_result(v, n_mpropdef, ret_type)
549 # Expr to read the result variable
550 var read_result = v.ast_builder.make_var_read(result_var, ret_type)
551 var return_expr = actual_block.n_expr.pop
552 # Adding the read return to argument
553 args.add(read_result)
554 var n_callcontract = v.ast_builder.make_call(n_self,callsite,args)
555 actual_block.add_all([n_callcontract,return_expr])
556 else
557 # build the call to the contract method
558 var n_callcontract = v.ast_builder.make_call(n_self,callsite,args)
559 actual_block.add n_callcontract
560 end
561 end
562 end
563
564 redef class MClass
565
566 # This method create an abstract method representation with this MMethodDef an this AMethoddef
567 private fun create_abstract_method(v: ContractsVisitor, mproperty: MMethod, mclassdef: MClassDef, msignature: nullable MSignature, n_signature: ASignature): AMethPropdef
568 do
569 # new methoddef definition
570 var m_def = new MMethodDef(mclassdef, mproperty, v.current_location)
571 # set the signature
572 if msignature != null then m_def.msignature = msignature.clone
573 # set the abstract flag
574 m_def.is_abstract = true
575 # Build the new node method
576 var n_def = v.ast_builder.make_method(null,null,m_def,n_signature,null,null,null,null)
577 # Define the location of the new method for corresponding of the actual method
578 n_def.location = v.current_location
579 # Association new npropdef to mpropdef
580 v.toolcontext.modelbuilder.unsafe_add_mpropdef2npropdef(m_def,n_def)
581 return n_def
582 end
583
584 # Create method with an empty block
585 # 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
586 private fun create_empty_method(v: ContractsVisitor, mproperty: MMethod, mclassdef: MClassDef, msignature: nullable MSignature, n_signature: ASignature): AMethPropdef
587 do
588 var n_def = create_abstract_method(v, mproperty, mclassdef, msignature, n_signature)
589 n_def.mpropdef.is_abstract = false
590 n_def.n_block = v.ast_builder.make_block
591 return n_def
592 end
593 end
594
595 redef class MMethodDef
596
597 # Verification of the contract facet
598 # Check if a contract facet already exists to use it again or if it is necessary to create it.
599 private fun check_contract_facet(v: ContractsVisitor, n_signature: ASignature, classdef: MClassDef, mcontract: MContract, exist_contract: Bool)
600 do
601 var exist_contract_facet = mproperty.has_contract_facet
602 if exist_contract_facet and exist_contract then return
603
604 var contract_facet: AMethPropdef
605 if not exist_contract_facet then
606 # If has no contract facet in intro just create it
607 if classdef != mproperty.intro_mclassdef then create_contract_facet(v, mproperty.intro_mclassdef, n_signature)
608 # If has no contract facet just create it
609 contract_facet = create_contract_facet(v, classdef, n_signature)
610 else
611 # Check if the contract facet already exist in this context (in this classdef)
612 if classdef.mpropdefs_by_property.has_key(mproperty.mcontract_facet) then
613 # get the define
614 contract_facet = v.toolcontext.modelbuilder.mpropdef2node(classdef.mpropdefs_by_property[mproperty.mcontract_facet]).as(AMethPropdef)
615 else
616 # create a new contract facet definition
617 contract_facet = create_contract_facet(v, classdef, n_signature)
618 var block = v.ast_builder.make_block
619 # super call to the contract facet
620 var n_super_call = v.ast_builder.make_super_call(n_signature.make_parameter_read(v.ast_builder), null)
621 # verification for add a return or not
622 if contract_facet.mpropdef.msignature.return_mtype != null then
623 block.add(v.ast_builder.make_return(n_super_call))
624 else
625 block.add(n_super_call)
626 end
627 contract_facet.n_block = block
628 end
629 end
630 contract_facet.adapt_block_to_contract(v, mcontract, contract_facet)
631 contract_facet.location = v.current_location
632 contract_facet.do_all(v.toolcontext)
633 end
634
635 # Method to create a contract facet of the method
636 private fun create_contract_facet(v: ContractsVisitor, classdef: MClassDef, n_signature: ASignature): AMethPropdef
637 do
638 var contract_facet = mproperty.build_contract_facet
639 # Defines the contract phase is an init or not
640 # it is necessary to use the contracts on constructor
641 contract_facet.is_init = self.mproperty.is_init
642
643 # check if the method has an `msignature` to copy it.
644 var m_signature: nullable MSignature = null
645 if mproperty.intro.msignature != null then m_signature = mproperty.intro.msignature.clone
646
647 var n_contractdef = classdef.mclass.create_empty_method(v, contract_facet, classdef, m_signature, n_signature)
648 # FIXME set the location because the callsite creation need the node location
649 n_contractdef.location = v.current_location
650 n_contractdef.validate
651
652 var block = v.ast_builder.make_block
653 var n_self = new ASelfExpr
654 var args = n_contractdef.n_signature.make_parameter_read(v.ast_builder)
655 var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_contractdef, mproperty, true)
656 var n_call = v.ast_builder.make_call(n_self, callsite, args)
657
658 if m_signature.return_mtype == null then
659 block.add(n_call)
660 else
661 block.add(v.ast_builder.make_return(n_call))
662 end
663
664 n_contractdef.n_block = block
665 n_contractdef.do_all(v.toolcontext)
666 return n_contractdef
667 end
668
669 # Entry point to build contract (define the contract facet and define the contract method verification)
670 private fun construct_contract(v: ContractsVisitor, n_signature: ASignature, n_annotation: AAnnotation, mcontract: MContract, exist_contract: Bool)
671 do
672 if check_same_contract(v, n_annotation, mcontract) then return
673 if not exist_contract and not is_intro then no_intro_contract(v, n_signature, mcontract, n_annotation)
674 v.define_signature(mcontract, n_signature, mproperty.intro.msignature)
675
676 var conditiondef = v.build_contract(n_annotation, mcontract, mclassdef)
677 check_contract_facet(v, n_signature.clone, mclassdef, mcontract, exist_contract)
678 has_contract = true
679 end
680
681 # Create a contract on the introduction classdef of the property.
682 # Display an warning message if necessary
683 private fun no_intro_contract(v: ContractsVisitor, n_signature: ASignature, mcontract: MContract, n_annotation: AAnnotation)
684 do
685 mcontract.create_empty_contract(v, mcontract.intro_mclassdef, mcontract.adapt_msignature(self.mproperty.intro.msignature), mcontract.adapt_nsignature(n_signature))
686 mcontract.no_intro_contract(v, n_annotation)
687 mproperty.intro.has_contract = true
688 end
689
690 # Is the contract already defined in the context
691 #
692 # Exemple :
693 # fun foo is expect([...]), expect([...])
694 #
695 # Here `check_same_contract` display an error when the second expect is processed
696 private fun check_same_contract(v: ContractsVisitor, n_annotation: AAnnotation ,mcontract: MContract): Bool
697 do
698 if self.mclassdef.mpropdefs_by_property.has_key(mcontract) then
699 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}")
700 return true
701 end
702 return false
703 end
704 end
705
706 redef class MPropDef
707 # flag to indicate is the `MPropDef` has a contract
708 var has_contract = false
709 end
710
711 redef class APropdef
712 redef fun check_callsite(v)
713 do
714 v.visited_method = self
715 end
716 end
717
718 redef class AMethPropdef
719
720 # Entry point to create a contract (verification of inheritance, or new contract).
721 redef fun create_contracts(v)
722 do
723 v.ast_builder.check_mmodule(mpropdef.mclassdef.mmodule)
724
725 v.current_location = self.location
726 v.is_intro_contract = mpropdef.is_intro
727
728 if n_annotations != null then
729 for n_annotation in n_annotations.n_items do
730 check_annotation(v,n_annotation)
731 end
732 end
733
734 if not mpropdef.is_intro and not v.find_no_contract then
735 self.check_redef(v)
736 end
737
738 # reset the flag
739 v.find_no_contract = false
740 end
741
742 # Verification of the annotation to know if it is a contract annotation.
743 # If this is the case, we built the appropriate contract.
744 private fun check_annotation(v: ContractsVisitor, n_annotation: AAnnotation)
745 do
746 if n_annotation.name == "expect" then
747 if not v.check_usage_expect(mpropdef.mclassdef.mmodule) then return
748 var exist_contract = mpropdef.mproperty.has_expect
749 mpropdef.construct_contract(v, self.n_signature.as(not null), n_annotation, mpropdef.mproperty.build_expect, exist_contract)
750 else if n_annotation.name == "ensure" then
751 if not v.check_usage_ensure(mpropdef.mclassdef.mmodule) then return
752 var exist_contract = mpropdef.mproperty.has_ensure
753 mpropdef.construct_contract(v, self.n_signature.as(not null), n_annotation, mpropdef.mproperty.build_ensure, exist_contract)
754 else if n_annotation.name == "no_contract" then
755 # no_contract found set the flag to true
756 v.find_no_contract = true
757 end
758 end
759
760 # Verification if the method have an inherited contract to apply it.
761 private fun check_redef(v: ContractsVisitor)
762 do
763 # Check if the method has an attached contract
764 if not mpropdef.has_contract then
765 if mpropdef.mproperty.intro.has_contract then
766 mpropdef.has_contract = true
767 end
768 end
769 end
770
771 # Adapt the block to use the contracts
772 private fun adapt_block_to_contract(v: ContractsVisitor, contract: MContract, n_mpropdef: AMethPropdef)
773 do
774 contract.adapt_block_to_contract(v, n_mpropdef)
775 mpropdef.has_contract = true
776 n_mpropdef.do_all(v.toolcontext)
777 end
778 end
779
780 redef class MSignature
781
782 # Adapt signature for an contract
783 #
784 # The returned `MSignature` is the copy of `self` without return type.
785 private fun adapt_to_contract: MSignature do return new MSignature(mparameters.to_a, null)
786
787 # Adapt signature for a ensure contract
788 #
789 # The returned `MSignature` is the copy of `self` without return type.
790 # The return type is replaced by a new parameter `result`
791 private fun adapt_to_ensurecondition: MSignature
792 do
793 var rtype = return_mtype
794 var msignature = adapt_to_contract
795 if rtype != null then
796 msignature.mparameters.add(new MParameter("result", rtype, false))
797 end
798 return msignature
799 end
800
801 # The returned `MSignature` is the exact copy of `self`.
802 private fun clone: MSignature do return new MSignature(mparameters.to_a, return_mtype)
803 end
804
805 redef class ASignature
806
807 # Create an array of AVarExpr representing the read of every parameters
808 private fun make_parameter_read(ast_builder: ASTBuilder): Array[AVarExpr]
809 do
810 var args = new Array[AVarExpr]
811 for n_param in self.n_params do
812 var mtype = n_param.variable.declared_type
813 var variable = n_param.variable
814 if variable != null and mtype != null then
815 args.push ast_builder.make_var_read(variable, mtype)
816 end
817 end
818 return args
819 end
820
821 # Create a new ASignature adapted for contract
822 #
823 # The returned `ASignature` is the copy of `self` without return type.
824 private fun adapt_to_contract: ASignature
825 do
826 var adapt_nsignature = self.clone
827 if adapt_nsignature.n_type != null then adapt_nsignature.n_type.detach
828 return adapt_nsignature
829 end
830
831 # Create a new ASignature adapted for ensure
832 #
833 # The returned `ASignature` is the copy of `self` without return type.
834 # The return type is replaced by a new parameter `result`
835 private fun adapt_to_ensurecondition: ASignature do
836 var nsignature = adapt_to_contract
837 if ret_type != null then
838 var variable = new Variable("result")
839 variable.declared_type = ret_type
840 nsignature.n_params.add new AParam.make(variable, ret_type.create_ast_representation)
841 end
842 return nsignature
843 end
844 end
845
846 redef class ASendExpr
847 redef fun check_callsite(v)
848 do
849 var actual_callsite = callsite
850 if actual_callsite != null then
851 callsite = v.drive_method_contract(actual_callsite)
852 end
853 end
854 end
855
856 redef class ANewExpr
857 redef fun check_callsite(v)
858 do
859 var actual_callsite = callsite
860 if actual_callsite != null then
861 callsite = v.drive_method_contract(actual_callsite)
862 end
863 end
864 end