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