contracts: Remove `contract_name` property
[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 # Method use to diplay warning when the contract is not present at the introduction
300 private fun no_intro_contract(v: ContractsVisitor, a: Array[AAnnotation])do end
301
302 # Creating specific inheritance block contract
303 #
304 # `super_args` : Correspond to the `super` call arguments
305 private fun create_inherit_nblock(v: ContractsVisitor, n_conditions: Array[AExpr], super_args: Array[AExpr]): ABlockExpr is abstract
306
307 # Method to adapt the `n_mpropdef.n_block` to the contract
308 private fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef) is abstract
309
310 # Adapt the msignature specifically for the contract method
311 private fun adapt_specific_msignature(m_signature: MSignature): MSignature do return m_signature.adapt_to_contract
312
313 # Adapt the nsignature specifically for the contract method
314 private fun adapt_specific_nsignature(n_signature: ASignature): ASignature do return n_signature.adapt_to_contract
315
316 # Adapt the `m_signature` to the contract
317 # If it is not null call the specific adapt `m_signature` for the contract
318 private fun adapt_msignature(m_signature: nullable MSignature): MSignature
319 do
320 if m_signature == null then return new MSignature(new Array[MParameter])
321 return adapt_specific_msignature(m_signature)
322 end
323
324 # Adapt the `n_signature` to the contract
325 # If it is not null call the specific adapt `n_signature` for the contract
326 private fun adapt_nsignature(n_signature: nullable ASignature): ASignature
327 do
328 if n_signature == null then return new ASignature
329 return adapt_specific_nsignature(n_signature)
330 end
331
332 # Create a new empty contract
333 private fun create_empty_contract(v: ContractsVisitor, mclassdef: MClassDef, msignature: nullable MSignature, n_signature: ASignature)
334 do
335 var n_contract_def = intro_mclassdef.mclass.create_empty_method(v, self, mclassdef, msignature, n_signature)
336 n_contract_def.do_all(v.toolcontext)
337 end
338
339 # Create the initial contract (intro)
340 # All contracts have the same implementation for the introduction.
341 #
342 # Example:
343 # ~~~nitish
344 # fun contrat([...])
345 # do
346 # assert contract_condition
347 # end
348 # ~~~
349 #
350 private fun create_intro_contract(v: ContractsVisitor, n_conditions: Array[AExpr], mclassdef: MClassDef)
351 do
352 var n_block = v.ast_builder.make_block
353 for n_condition in n_conditions do
354 # Create a new tid to set the name of the assert for more explicit error
355 var tid = new TId.init_tk(n_condition.location)
356 tid.text = "{n_condition.location.text}"
357 var n_assert = v.ast_builder.make_assert(tid, n_condition, null)
358 # Define the assert location to reference the annoation
359 n_assert.location = n_condition.location
360 n_block.add n_assert
361 end
362 make_contract(v, n_block, mclassdef)
363 end
364
365 # Create a contract to check the old (super call) and the new conditions
366 #
367 # Example:
368 # ~~~nitish
369 # fun contrat([...])
370 # do
371 # super # Call the old contracts
372 # assert new_condition
373 # end
374 # ~~~
375 #
376 private fun create_subcontract(v: ContractsVisitor, n_conditions: Array[AExpr], mclassdef: MClassDef)
377 do
378 var args = v.n_signature.make_parameter_read(v.ast_builder)
379 var n_block = v.ast_builder.make_block
380 n_block = self.create_inherit_nblock(v, n_conditions, args)
381 make_contract(v, n_block, mclassdef)
382 end
383
384 # Build a method with a specific block `n_block` in a specified `mclassdef`
385 private fun make_contract(v: ContractsVisitor, n_block: AExpr, mclassdef: MClassDef)
386 do
387 var n_contractdef = intro_mclassdef.mclass.create_empty_method(v, self, mclassdef, v.m_signature, v.n_signature)
388 n_contractdef.n_block = n_block
389 # Define the location of the new method for corresponding of the annotation location
390 n_contractdef.location = v.current_location
391 n_contractdef.do_all(v.toolcontext)
392 end
393 end
394
395 redef class MExpect
396
397 # Display warning if no contract is defined at introduction `expect`,
398 # because if no contract is defined at the introduction the added
399 # contracts will not cause any error even if they are not satisfied.
400 #
401 # Example:
402 # ~~~nitish
403 # class A
404 # fun bar [...]
405 # fun _bar_expect([...])
406 # do
407 # [empty contract]
408 # end
409 # end
410 #
411 # redef class A
412 # redef fun bar is expect(contract_condition)
413 # redef fun _bar_expect([...])
414 # do
415 # if (contract_condition) then return
416 # super
417 # end
418 # end
419 # ~~~~
420 #
421 redef fun no_intro_contract(v: ContractsVisitor, a: Array[AAnnotation])
422 do
423 v.toolcontext.warning(a.first.location,"useless_contract","Useless contract: No contract defined at the introduction of the method")
424 end
425
426 redef fun create_inherit_nblock(v: ContractsVisitor, n_conditions: Array[AExpr], super_args: Array[AExpr]): ABlockExpr
427 do
428 var n_block = v.ast_builder.make_block
429 for n_condition in n_conditions do
430 # Creating the if expression with the new condition
431 var if_block = v.ast_builder.make_if(n_condition, n_condition.mtype)
432 # Creating and adding return expr to the then case
433 if_block.n_then = v.ast_builder.make_return
434 if_block.location = n_condition.location
435 n_block.add if_block
436 end
437 n_block.add v.ast_builder.make_super_call(super_args)
438 return n_block
439 end
440
441 redef fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef)
442 do
443 var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_mpropdef, self, true)
444 var n_self = new ASelfExpr
445 var args = n_mpropdef.n_signature.make_parameter_read(v.ast_builder)
446 var n_callexpect = v.ast_builder.make_call(n_self,callsite,args)
447 # Creation of the new instruction block with the call to expect condition
448 var actual_expr = n_mpropdef.n_block
449 var new_block = new ABlockExpr
450 new_block.n_expr.push n_callexpect
451 if actual_expr isa ABlockExpr then
452 new_block.n_expr.add_all(actual_expr.n_expr)
453 else if actual_expr != null then
454 new_block.n_expr.push(actual_expr)
455 end
456 n_mpropdef.n_block = new_block
457 end
458 end
459
460 redef class BottomMContract
461
462 redef fun create_inherit_nblock(v: ContractsVisitor, n_conditions: Array[AExpr], super_args: Array[AExpr]): ABlockExpr
463 do
464 # Define contract block
465 var n_block = v.ast_builder.make_block
466
467 var super_call = v.ast_builder.make_super_call(super_args)
468
469 n_block.add super_call
470 for n_condition in n_conditions do
471 var tid = new TId.init_tk(n_condition.location)
472 tid.text = "{n_condition.location.text}"
473 # Creating the assert expression with the new condition
474 var n_assert = v.ast_builder.make_assert(tid, n_condition)
475 n_assert.location = n_condition.location
476 n_block.add n_assert
477 end
478 return n_block
479 end
480
481 # Inject the `result` variable into the `n_block` of the given n_mpropdef`.
482 #
483 # The purpose of the variable is to capture return values to use it in contracts.
484 private fun inject_result(v: ContractsVisitor, n_mpropdef: AMethPropdef, ret_type: MType): Variable
485 do
486 var actual_block = n_mpropdef.n_block
487 # never happen. If it's the case the problem is in the contract facet building
488 assert actual_block isa ABlockExpr
489
490 var return_var: nullable Variable = null
491
492 var return_expr = actual_block.n_expr.last.as(AReturnExpr)
493
494 var returned_expr = return_expr.n_expr
495 # The return node has no returned expression
496 assert returned_expr != null
497
498 # Check if the result variable already exit
499 if returned_expr isa AVarExpr then
500 if returned_expr.variable.name == "result" then
501 return_var = returned_expr.variable
502 end
503 end
504
505 return_var = new Variable("result")
506 # Creating a new variable to keep the old return of the method
507 var assign_result = v.ast_builder.make_var_assign(return_var, returned_expr)
508 # Remove the actual return
509 actual_block.n_expr.pop
510 # Set the return type
511 return_var.declared_type = ret_type
512 # Adding the reading expr of result variable to the block
513 actual_block.add assign_result
514 # Expr to read the result variable
515 var read_result = v.ast_builder.make_var_read(return_var, ret_type)
516 # Definition of the new return
517 return_expr = v.ast_builder.make_return(read_result)
518 actual_block.add return_expr
519 return return_var
520 end
521 end
522
523 redef class MEnsure
524
525 redef fun adapt_specific_msignature(m_signature: MSignature): MSignature
526 do
527 return m_signature.adapt_to_ensurecondition
528 end
529
530 redef fun adapt_specific_nsignature(n_signature: ASignature): ASignature
531 do
532 return n_signature.adapt_to_ensurecondition
533 end
534
535 redef fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef)
536 do
537 var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_mpropdef, self, true)
538 var n_self = new ASelfExpr
539 # argument to call the contract method
540 var args = n_mpropdef.n_signature.make_parameter_read(v.ast_builder)
541 # Inject the variable result
542 # The cast is always safe because the online adapted method is the contract facet
543
544 var actual_block = n_mpropdef.n_block
545 # never happen. If it's the case the problem is in the contract facet building
546 assert actual_block isa ABlockExpr
547
548 var ret_type = n_mpropdef.mpropdef.mproperty.intro.msignature.return_mtype
549 if ret_type != null then
550 var result_var = inject_result(v, n_mpropdef, ret_type)
551 # Expr to read the result variable
552 var read_result = v.ast_builder.make_var_read(result_var, ret_type)
553 var return_expr = actual_block.n_expr.pop
554 # Adding the read return to argument
555 args.add(read_result)
556 var n_callcontract = v.ast_builder.make_call(n_self,callsite,args)
557 actual_block.add_all([n_callcontract,return_expr])
558 else
559 # build the call to the contract method
560 var n_callcontract = v.ast_builder.make_call(n_self,callsite,args)
561 actual_block.add n_callcontract
562 end
563 end
564 end
565
566 redef class MClass
567
568 # This method create an abstract method representation with this MMethodDef an this AMethoddef
569 private fun create_abstract_method(v: ContractsVisitor, mproperty: MMethod, mclassdef: MClassDef, msignature: nullable MSignature, n_signature: ASignature): AMethPropdef
570 do
571 # new methoddef definition
572 var m_def = new MMethodDef(mclassdef, mproperty, v.current_location)
573 # set the signature
574 if msignature != null then m_def.msignature = msignature.clone
575 # set the abstract flag
576 m_def.is_abstract = true
577 # Build the new node method
578 var n_def = v.ast_builder.make_method(null,null,m_def,n_signature,null,null,null,null)
579 # Define the location of the new method for corresponding of the actual method
580 n_def.location = v.current_location
581 # Association new npropdef to mpropdef
582 v.toolcontext.modelbuilder.unsafe_add_mpropdef2npropdef(m_def,n_def)
583 return n_def
584 end
585
586 # Create method with an empty block
587 # 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
588 private fun create_empty_method(v: ContractsVisitor, mproperty: MMethod, mclassdef: MClassDef, msignature: nullable MSignature, n_signature: ASignature): AMethPropdef
589 do
590 var n_def = create_abstract_method(v, mproperty, mclassdef, msignature, n_signature)
591 n_def.mpropdef.is_abstract = false
592 n_def.n_block = v.ast_builder.make_block
593 return n_def
594 end
595 end
596
597 redef class MMethodDef
598
599 # Verification of the contract facet
600 # Check if a contract facet already exists to use it again or if it is necessary to create it.
601 private fun check_contract_facet(v: ContractsVisitor, n_signature: ASignature, classdef: MClassDef, mcontract: MContract, exist_contract: Bool)
602 do
603 var exist_contract_facet = mproperty.has_contract_facet
604 if exist_contract_facet and exist_contract then return
605
606 var contract_facet: AMethPropdef
607 if not exist_contract_facet then
608 # If has no contract facet in intro just create it
609 if classdef != mproperty.intro_mclassdef then create_contract_facet(v, mproperty.intro_mclassdef, n_signature)
610 # If has no contract facet just create it
611 contract_facet = create_contract_facet(v, classdef, n_signature)
612 else
613 # Check if the contract facet already exist in this context (in this classdef)
614 if classdef.mpropdefs_by_property.has_key(mproperty.mcontract_facet) then
615 # get the define
616 contract_facet = v.toolcontext.modelbuilder.mpropdef2node(classdef.mpropdefs_by_property[mproperty.mcontract_facet]).as(AMethPropdef)
617 else
618 # create a new contract facet definition
619 contract_facet = create_contract_facet(v, classdef, n_signature)
620 var block = v.ast_builder.make_block
621 # super call to the contract facet
622 var n_super_call = v.ast_builder.make_super_call(n_signature.make_parameter_read(v.ast_builder), null)
623 # verification for add a return or not
624 if contract_facet.mpropdef.msignature.return_mtype != null then
625 block.add(v.ast_builder.make_return(n_super_call))
626 else
627 block.add(n_super_call)
628 end
629 contract_facet.n_block = block
630 end
631 end
632 contract_facet.adapt_block_to_contract(v, mcontract, contract_facet)
633 contract_facet.location = v.current_location
634 contract_facet.do_all(v.toolcontext)
635 end
636
637 # Method to create a contract facet of the method
638 private fun create_contract_facet(v: ContractsVisitor, classdef: MClassDef, n_signature: ASignature): AMethPropdef
639 do
640 var contract_facet = mproperty.build_contract_facet
641 # Defines the contract phase is an init or not
642 # it is necessary to use the contracts on constructor
643 contract_facet.is_init = self.mproperty.is_init
644
645 # check if the method has an `msignature` to copy it.
646 var m_signature: nullable MSignature = null
647 if mproperty.intro.msignature != null then m_signature = mproperty.intro.msignature.clone
648
649 var n_contractdef = classdef.mclass.create_empty_method(v, contract_facet, classdef, m_signature, n_signature)
650 # FIXME set the location because the callsite creation need the node location
651 n_contractdef.location = v.current_location
652 n_contractdef.validate
653
654 var block = v.ast_builder.make_block
655 var n_self = new ASelfExpr
656 var args = n_contractdef.n_signature.make_parameter_read(v.ast_builder)
657 var callsite = v.ast_builder.create_callsite(v.toolcontext.modelbuilder, n_contractdef, mproperty, true)
658 var n_call = v.ast_builder.make_call(n_self, callsite, args)
659
660 if m_signature.return_mtype == null then
661 block.add(n_call)
662 else
663 block.add(v.ast_builder.make_return(n_call))
664 end
665
666 n_contractdef.n_block = block
667 n_contractdef.do_all(v.toolcontext)
668 return n_contractdef
669 end
670
671 # Entry point to build contract (define the contract facet and define the contract method verification)
672 private fun construct_contract(v: ContractsVisitor, n_signature: ASignature, n_annotations: Array[AAnnotation], mcontract: MContract, exist_contract: Bool)
673 do
674 if not exist_contract and not is_intro then no_intro_contract(v, mcontract, n_annotations)
675 v.define_signature(mcontract, n_signature, mproperty.intro.msignature)
676
677 v.build_contract(n_annotations, mcontract, mclassdef)
678 check_contract_facet(v, n_signature.clone, mclassdef, mcontract, exist_contract)
679 has_contract = true
680 end
681
682 # Create a contract on the introduction classdef of the property.
683 # Display an warning message if necessary
684 private fun no_intro_contract(v: ContractsVisitor, mcontract: MContract, n_annotations: Array[AAnnotation])
685 do
686 v.toolcontext.modelbuilder.create_method_from_property(mcontract, mcontract.intro_mclassdef, false, v.m_signature)
687 mcontract.no_intro_contract(v, n_annotations)
688 end
689
690 # Apply the `no_contract` annotation to the contract. This method removes the inheritance by adding an empty contract method.
691 # Display a warning if the annotation is not needed
692 private fun no_contract_apply(v: ContractsVisitor, n_signature: ASignature)
693 do
694 var mensure = mproperty.mensure
695 var mexpect = mproperty.mexpect
696 if mensure == null and mexpect == null then
697 v.toolcontext.warning(location, "useless_nocontract", "Useless `no_contract`, no contract was declared for `{name}`. Remove the `no_contract`")
698 end
699 if mensure != null then
700 # Add an empty ensure method to replace the actual definition
701 v.toolcontext.modelbuilder.create_method_from_property(mensure, mclassdef, false, mensure.intro.msignature)
702 end
703 if mexpect != null then
704 # Add an empty expect method to replace the actual definition
705 v.toolcontext.modelbuilder.create_method_from_property(mexpect, mclassdef, false, mexpect.intro.msignature)
706 end
707 end
708 end
709
710 redef class MPropDef
711 # flag to indicate is the `MPropDef` has a contract
712 var has_contract = false
713 end
714
715 redef class APropdef
716 redef fun check_callsite(v)
717 do
718 v.visited_propdef = self
719 end
720 end
721
722 redef class AMethPropdef
723
724 # Entry point to create a contract (verification of inheritance, or new contract).
725 redef fun create_contracts(v)
726 do
727 v.ast_builder.check_mmodule(mpropdef.mclassdef.mmodule)
728 v.current_location = self.location
729 v.is_intro_contract = mpropdef.is_intro
730 check_annotation(v)
731 if not mpropdef.is_intro then check_redef(v)
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)
737 do
738 var annotations_expect = get_annotations("expect")
739 var annotations_ensure = get_annotations("ensure")
740 var annotation_no_contract = get_annotations("no_contract")
741
742 if (not annotations_expect.is_empty or not annotations_ensure.is_empty) and not annotation_no_contract.is_empty then
743 v.toolcontext.error(location, "The new contract definition is not correct when using `no_contract`. Remove the contract definition or the `no_contract`")
744 return
745 end
746
747 var nsignature = n_signature or else new ASignature
748
749 if not annotation_no_contract.is_empty then
750 mpropdef.no_contract_apply(v, nsignature.clone)
751 return
752 end
753
754 if not annotations_expect.is_empty then
755 var exist_contract = mpropdef.mproperty.has_expect
756 mpropdef.construct_contract(v, nsignature.clone, annotations_expect, mpropdef.mproperty.build_expect, exist_contract)
757 end
758
759 if not annotations_ensure.is_empty then
760 var exist_contract = mpropdef.mproperty.has_ensure
761 mpropdef.construct_contract(v, nsignature.clone, annotations_ensure, mpropdef.mproperty.build_ensure, exist_contract)
762 end
763 end
764
765 # Verification if the method have an inherited contract to apply it.
766 private fun check_redef(v: ContractsVisitor)
767 do
768 # Check if the method has an attached contract
769 if not mpropdef.has_contract then
770 if mpropdef.mproperty.intro.has_contract then
771 mpropdef.has_contract = true
772 end
773 end
774 end
775
776 # Adapt the block to use the contracts
777 private fun adapt_block_to_contract(v: ContractsVisitor, contract: MContract, n_mpropdef: AMethPropdef)
778 do
779 contract.adapt_block_to_contract(v, n_mpropdef)
780 mpropdef.has_contract = true
781 n_mpropdef.do_all(v.toolcontext)
782 end
783 end
784
785 redef class MSignature
786
787 # Adapt signature for an contract
788 #
789 # The returned `MSignature` is the copy of `self` without return type.
790 private fun adapt_to_contract: MSignature do return new MSignature(mparameters.to_a, null)
791
792 # Adapt signature for a ensure contract
793 #
794 # The returned `MSignature` is the copy of `self` without return type.
795 # The return type is replaced by a new parameter `result`
796 private fun adapt_to_ensurecondition: MSignature
797 do
798 var rtype = return_mtype
799 var msignature = adapt_to_contract
800 if rtype != null then
801 msignature.mparameters.add(new MParameter("result", rtype, false))
802 end
803 return msignature
804 end
805
806 # The returned `MSignature` is the exact copy of `self`.
807 private fun clone: MSignature do return new MSignature(mparameters.to_a, return_mtype)
808 end
809
810 redef class ASignature
811
812 # Create an array of AVarExpr representing the read of every parameters
813 private fun make_parameter_read(ast_builder: ASTBuilder): Array[AVarExpr]
814 do
815 var args = new Array[AVarExpr]
816 for n_param in self.n_params do
817 var mtype = n_param.variable.declared_type
818 var variable = n_param.variable
819 if variable != null and mtype != null then
820 args.push ast_builder.make_var_read(variable, mtype)
821 end
822 end
823 return args
824 end
825
826 # Create a new ASignature adapted for contract
827 #
828 # The returned `ASignature` is the copy of `self` without return type.
829 private fun adapt_to_contract: ASignature
830 do
831 var adapt_nsignature = self.clone
832 if adapt_nsignature.n_type != null then adapt_nsignature.n_type.detach
833 return adapt_nsignature
834 end
835
836 # Create a new ASignature adapted for ensure
837 #
838 # The returned `ASignature` is the copy of `self` without return type.
839 # The return type is replaced by a new parameter `result`
840 private fun adapt_to_ensurecondition: ASignature do
841 var nsignature = adapt_to_contract
842 if ret_type != null then
843 var variable = new Variable("result")
844 variable.declared_type = ret_type
845 nsignature.n_params.add new AParam.make(variable, ret_type.create_ast_representation)
846 end
847 return nsignature
848 end
849 end
850
851 redef class ASendExpr
852 redef fun check_callsite(v)
853 do
854 var actual_callsite = callsite
855 if actual_callsite != null then
856 callsite = v.drive_method_contract(actual_callsite)
857 end
858 end
859 end
860
861 redef class ANewExpr
862 redef fun check_callsite(v)
863 do
864 var actual_callsite = callsite
865 if actual_callsite != null then
866 callsite = v.drive_method_contract(actual_callsite)
867 end
868 end
869 end