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