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