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