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