filename | ./lib/contract/integration.rb |
total coverage | 46.6 |
code coverage | 43.9 |
1 # This file contains code for integrating the contract library with built-in
2 # classes and methods.
3 #
4 # See Contract::Check, Module#signature and Module#fulfills.
5
6
7 class Contract < Test::Unit::TestCase
8 # Implements checks that can for example be used in Module#signature
9 # specifications. They are implemented simply by overriding the === case
10 # equality operator. They can also be nested like this:
11 # # Matches something that is an Enumerable and that responds to
12 # # either :to_ary or :to_a.
13 # signature :x, Contract::Check::All[
14 # Enumerable,
15 # Contract::Check::Any[
16 # Contract::Check::Quack[:to_a],
17 # Contract::Check::Quack[:to_ary]
18 # ]
19 # ]
20 module Check
21 # An abstract Base class for Contract::Check classes.
22 # Contains logic for instantation.
23 class Base # :nodoc:
24 class << self
25 alias :[] :new
26 end
27
28 def initialize(*args, &block)
29 @args, @block = args, block
30 end
31 end
32
33 # Checks that the specified block matches.
34 # Example:
35 # signature :x, Contract::Check.block { |arg| arg > 0 }
36 class Block < Base
37 def ===(other)
38 @block.call(other)
39 end
40 end
41 # Short-cut for creating a Contract::Check::Block.
42 def self.block(&block) # :yields: arg
43 Block.new(&block)
44 end
45
46 # Checks that all the specified methods are answered.
47 # Example:
48 # signature :x, Contract::Check::Quack[:to_sym]
49 class Quack < Base
50 def ===(other)
51 @args.all? { |arg| other.respond_to?(arg) }
52 end
53 end
54
55 # Checks that all the specified conditions match.
56 # Example:
57 # signature :x, Contract::Check::All[Array, Enumerable]
58 class All < Base
59 def ===(other)
60 @args.all? { |arg| arg === other }
61 end
62 end
63 # Alias for Contract::Check::All
64 And = All unless defined?(And)
65
66 # Checks that at least one of the specified conditions match.
67 # Example:
68 # signature :x, Contract::Check::Any[String, Symbol]
69 class Any < Base
70 def ===(other)
71 @args.any? { |arg| arg === other }
72 end
73 end
74 # Alias for Contract::Check::Any
75 Or = Any unless defined?(Or)
76
77 # Checks that none of the specified conditions match.
78 # Example:
79 # signature :x, Contract::Check::None[Numeric, Symbol]
80 # signature :x, Contract::Check::Not[Comparable]
81 class None < Base
82 def ===(other)
83 not @args.any? { |arg| arg === other }
84 end
85 end
86 # Alias for Contract::Check::None
87 Not = None unless defined?(Not)
88 end
89
90 class << self
91 # Whether signatures should be checked. By default signatures are checked
92 # only when the application is run in $DEBUG mode. (By specifying the -d
93 # switch on the invocation of Ruby.)
94 #
95 # Note: If you want to change this you need to do so before doing any
96 # Module#signature calls or it will not be applied. It's probably best
97 # set right after requiring the contract library.
98 attr_accessor :check_signatures
99 alias :check_signatures? :check_signatures
100
101 # Whether fulfills should be checked. This is enabled by default.
102 #
103 # Note: If you want to change this you need to do so before doing any
104 # Module#fulfills calls or it will not be applied. It's probably best
105 # set right after requiring the contract library.
106 attr_accessor :check_fulfills
107 alias :check_fulfills? :check_fulfills
108
109 # All adaption routes.
110 attr_accessor :adaptions # :nodoc:
111 end
112 self.check_signatures = $DEBUG if self.check_signatures.nil?
113 self.check_fulfills = true if self.check_fulfills.nil?
114 if self.adaptions.nil? then
115 self.adaptions = Hash.new { |hash, key| hash[key] = Array.new }
116 end
117
118 # Tries to adapt the specified object to the specified type.
119 # Returns the old object if no suitable adaption route was found or if
120 # it already is of the specified type.
121 #
122 # This will only use adaptions where the :to part is equal to the specified
123 # type. No multi-step conversion will be performed.
124 def self.adapt(object, type)
125 return object if type === object
126
127 @adaptions[type].each do |adaption|
128 if adaption[:from] === object and
129 (adaption[:if].nil? or adaption[:if] === object)
130 then
131 result = adaption[:via].call(object)
132 return result if type === result
133 end
134 end
135
136 return object
137 end
138 end
139
140
141 class Module
142 # Checks that the arguments and return value of a method match the specified
143 # signature. Checks are only actually done when Contract.check_signatures is
144 # set to true or if the <code>:no_adaption</code> option is +false+. The
145 # method will return +true+ in case it actually inserted the signature check
146 # logic and +nil+ in case it didn't.
147 #
148 # You will usually specify one type specifier (<code>:any</code> which will
149 # allow anything to appear at that position of the argument list or something
150 # that implements the === case equality operator -- samples are Contracts,
151 # Ranges, Classes, Modules, Regexps, Contract::Checks and so on) per argument.
152 # You can also use objects that implement the +call+ method as type specifiers
153 # which includes Methods and Procs.
154 #
155 # If you don't use the <code>:repeated</code> or <code>:allow_trailing</code>
156 # options the method will take exactly as many arguments as there are type
157 # specifiers which means that <code>signature :a_method</code> enforces
158 # +a_method+ having exactly zero arguments.
159 #
160 # The checks are done by wrapping the type checks around the method.
161 # ArgumentError exceptions will be raised in case the signature contract is
162 # not adhered to by your caller.
163 #
164 # An ArgumentError exception will be raised in case the methods natural
165 # argument list size and the signature you specified via Module.signature are
166 # incompatible. (Note that they don't have to be completely equivalent, you
167 # can still have a method taking zero or more arguments and apply a signature
168 # that limits the actual argument count to three arguments.)
169 #
170 # This method can take quite a few options. Here's a complete list:
171 #
172 # <code>:return</code>::
173 # A return type that the method must comply to. Note that this check (if
174 # failed) will actually raise a StandardError instead of an ArgumentError
175 # because the failure likely lies in the method itself and not in what the
176 # caller did.
177 #
178 # <code>:block</code>::
179 # +true+ or +false+ -- whether the method must take a block or not. So
180 # specifying <code>:block => false</code> enforces that the method is not
181 # allowed to have a block supplied.
182 #
183 # <code>:allow_trailing</code>::
184 # +true+ or +false+ -- whether the argument list may contain trailing,
185 # unchecked arguments.
186 #
187 # <code>:repeated</code>::
188 # An Array that specifies arguments of a method that will be repeated over
189 # and over again at the end of the argument list.
190 #
191 # A good sample of this are Array#values_at which takes zero or or more
192 # Numeric arguments and Enumerable#zip which takes zero or more other
193 # Enumerable arguments.
194 #
195 # Note that the Array that was associated with the <code>:repeated</code>
196 # option must not be empty or an ArgumentError exception will be raised.
197 # If there's just one repeated type you can omit the Array and directly
198 # specify the type identifier.
199 #
200 # <code>:no_adaption</code>::
201 # +true+ or +false+ -- whether no type adaption should be performed.
202 #
203 # Usage:
204 # signature(:to_s) # no arguments
205 # signature(:+, :any) # one argument, type unchecked
206 # signature(:+, Fixnum) # one argument, type Fixnum
207 # signature(:+, NumericContract)
208 # signature(:+, 1 .. 10)
209 # signature(:sqrt, lambda { |arg| arg > 0 })
210 #
211 # signature(:each, :block => true) # has to have block
212 # signature(:to_i, :block => false) # not allowed to have block
213 # signature(:to_i, :result => Fixnum) # return value must be Fixnum
214 # signature(:zip, :allow_trailing => true) # unchecked trailing args
215 # signature(:zip, :repeated => [Enumerable]) # repeated trailing args
216 # signature(:zip, :repeated => Enumerable)
217 # # foo(3, 6, 4, 7) works; foo(5), foo(3, 2) etc. don't
218 # signature(:foo, :repeated => [1..4, 5..9])
219 def signature(method, *args)
220 options = {}
221 signature = args.dup
222 options.update(signature.pop) if signature.last.is_a?(Hash)
223
224 return if not Contract.check_signatures? and options[:no_adaption]
225
226 old_method = instance_method(method)
227 remove_method(method) if instance_methods(false).include?(method.to_s)
228
229 arity = old_method.arity
230 if arity != signature.size and
231 (arity >= 0 or signature.size < ~arity) then
232 raise(ArgumentError, "signature isn't compatible with arity")
233 end
234
235 # Normalizes specifiers to Objects that respond to === so that the run-time
236 # checks only have to deal with that case. Also checks that a specifier is
237 # actually valid.
238 convert_specifier = lambda do |item|
239 # Procs, Methods etc.
240 if item.respond_to?(:call) then
241 Contract::Check.block { |arg| item.call(arg) }
242 # Already okay
243 elsif item.respond_to?(:===) or item == :any then
244 item
245 # Unknown specifier
246 else
247 raise(ArgumentError, "unsupported argument specifier #{item.inspect}")
248 end
249 end
250
251 signature.map!(&convert_specifier)
252
253 if options.include?(:repeated) then
254 options[:repeated] = Array(options[:repeated])
255 if options[:repeated].size == 0 then
256 raise(ArgumentError, "repeated arguments may not be an empty Array")
257 else
258 options[:repeated].map!(&convert_specifier)
259 end
260 end
261
262 if options.include?(:return) then
263 options[:return] = convert_specifier.call(options[:return])
264 end
265
266 # We need to keep around references to our arguments because we will
267 # need to access them via ObjectSpace._id2ref so that they do not
268 # get garbage collected.
269 @signatures ||= Hash.new { |hash, key| hash[key] = Array.new }
270 @signatures[method] << [signature, options, old_method]
271
272 adapted = Proc.new do |obj, type, assign_to|
273 if options[:no_adaption] then
274 obj
275 elsif assign_to then
276 %{(#{assign_to} = Contract.adapt(#{obj}, #{type}))}
277 else
278 %{Contract.adapt(#{obj}, #{type})}
279 end
280 end
281
282 # We have to use class_eval so that signatures can be specified for
283 # methods taking blocks in Ruby 1.8. (This will be obsolete in 1.9)
284 # We also make the checks as efficient as we can.
285 code = %{
286 def #{method}(*args, &block)
287 old_args = args.dup
288
289 #{if options.include?(:block) then
290 if options[:block] then
291 %{raise(ArgumentError, "no block given") unless block}
292 else
293 %{raise(ArgumentError, "block given") if block}
294 end
295 end
296 }
297
298 #{if not(options[:allow_trailing] or options.include?(:repeated))
299 msg = "wrong number of arguments (\#{args.size} for " +
300 "#{signature.size})"
301 %{if args.size != #{signature.size} then
302 raise(ArgumentError, "#{msg}")
303 end
304 }
305 elsif signature.size > 0
306 msg = "wrong number of arguments (\#{args.size} for " +
307 "at least #{signature.size}"
308 %{if args.size < #{signature.size} then
309 raise(ArgumentError, "#{msg}")
310 end
311 }
312 end
313 }
314
315 #{index = 0
316 signature.map do |part|
317 next if part == :any
318 index += 1
319 msg = "argument #{index} (\#{arg.inspect}) does not match " +
320 "#{part.inspect}"
321 %{type = ObjectSpace._id2ref(#{part.object_id})
322 arg = args.shift
323 unless type === #{adapted[%{arg}, %{type}, %{old_args[#{index - 1}]}]}
324 raise(ArgumentError, "#{msg}")
325 end
326 }
327 end
328 }
329
330 #{if repeated = options[:repeated] then
331 msg = "argument \#{idx + #{signature.size}}" +
332 "(\#{arg.inspect}) does not match \#{part.inspect}"
333 %{parts = ObjectSpace._id2ref(#{repeated.object_id})
334 args.each_with_index do |arg, idx|
335 part = parts[idx % #{repeated.size}]
336 if part != :any and
337 not part === (#{adapted[%{arg}, %{part}, %{old_args[idx]}]})
338 then
339 raise(ArgumentError, "#{msg}")
340 end
341 end
342 }
343 end
344 }
345
346 result = ObjectSpace._id2ref(#{old_method.object_id}).bind(self).
347 call(*old_args, &block)
348 #{if rt = options[:return] and rt != :any then
349 msg = "return value (\#{result.inspect}) does not match #{rt.inspect}"
350 %{type = ObjectSpace._id2ref(#{rt.object_id})
351 unless type === #{adapted[%{result}, %{type}]}
352 raise(StandardError, "#{msg}")
353 end
354 }
355 end
356 }
357 end
358 }
359 class_eval code, "(signature check for #{old_method.inspect[/: (.+?)>\Z/, 1]})"
360
361 return true
362 end
363
364 # Specifies that this Module/Class fulfills one or more contracts. The contracts
365 # will automatically be verified after an instance has been successfully created.
366 # This only actually does the checks when Contract.check_fulfills is enabled.
367 # The method will return +true+ in case it actually inserted the check logic and
368 # +nil+ in case it didn't.
369 #
370 # Note that this works by overriding the #initialize method which means that you
371 # should either add the fulfills statements after your initialize method or call
372 # the previously defined initialize method from your new one.
373 def fulfills(*contracts)
374 return unless Contract.check_fulfills?
375
376 contracts.each do |contract|
377 contract.implications.each do |implication|
378 include implication
379 end
380 end
381
382 old_method = instance_method(:initialize)
383 remove_method(:initialize) if instance_methods(false).include?("initialize")
384
385 # Keep visible references around so that the GC will not eat these up.
386 @fulfills ||= Array.new
387 @fulfills << [contracts, old_method]
388
389 # Have to use class_eval because define_method does not allow methods to take
390 # blocks. This can be cleaned up when Ruby 1.9 has become current.
391 class_eval %{
392 def initialize(*args, &block)
393 ObjectSpace._id2ref(#{old_method.object_id}).bind(self).call(*args, &block)
394 ObjectSpace._id2ref(#{contracts.object_id}).each do |contract|
395 contract.enforce self
396 end
397 end
398 }, "(post initialization contract check for #{self.inspect})"
399
400 return true
401 end
402 end
403
404
405 module Kernel
406 # Adds an adaption route from the specified type to the specified type.
407 # Basic usage looks like this:
408 # adaption :from => StringIO, :to => String, :via => :read
409 #
410 # This method takes various options. Here's a complete list:
411 #
412 # <code>:from</code>::
413 # The type that can be converted from. Defaults to +self+ meaning you
414 # can safely omit it in Class, Module or Contract context.
415 #
416 # <code>:to</code>::
417 # The type that can be converted to. Defaults to +self+ meaning you
418 # can safely omit it in Class, Module or Contract context.
419 #
420 # Note that you need to specify either <code>:from</code> or
421 # <code>:to</code>.
422 #
423 # <code>:via</code>::
424 # How the <code>:from</code> type will be converted to the
425 # <code>:to</code> type. If this is a Symbol the conversion will be
426 # done by invoking the method identified by that Symbol on the
427 # source object. Otherwise this should be something that responds to
428 # the +call+ method (for example Methods and Procs) which will get
429 # the source object as its argument and which should return the
430 # target object.
431 #
432 # <code>:if</code>::
433 # The conversion can only be performed if this condition is met.
434 # This can either be something that implements the === case
435 # equivalence operator or something that implements the +call+
436 # method. So Methods, Procs, Modules, Classes and Contracts all
437 # make sense in this context. You can also specify a Symbol in
438 # which case the conversion can only be performed if the source
439 # object responds to the method identified by that Symbol.
440 #
441 # Note that the <code>:if</code> option will default to the same
442 # value as the <code>:via</code> option if the <code>:via</code>
443 # option is a Symbol.
444 #
445 # If you invoke this method with a block it will be used instead of
446 # the <code>:via</code> option.
447 #
448 # See Contract.adapt for how conversion look-ups are performed.
449 def adaption(options = {}, &block) # :yield: source_object
450 options = {
451 :from => self,
452 :to => self
453 }.merge(options)
454
455 if block then
456 if options.include?(:via) then
457 raise(ArgumentError, "Can't use both block and :via")
458 else
459 options[:via] = block
460 end
461 end
462
463 if options[:via].respond_to?(:to_sym) then
464 options[:via] = options[:via].to_sym
465 end
466
467 options[:if] ||= options[:via] if options[:via].is_a?(Symbol)
468
469 if options[:via].is_a?(Symbol) then
470 symbol = options[:via]
471 options[:via] = lambda { |obj| obj.send(symbol) }
472 end
473
474 if options[:if].respond_to?(:to_sym) then
475 options[:if] = options[:if].to_sym
476 end
477
478 if options[:if].is_a?(Symbol) then
479 options[:if] = Contract::Check::Quack[options[:if]]
480 elsif options[:if].respond_to?(:call) then
481 callable = options[:if]
482 options[:if] = Contract::Check.block { |obj| callable.call(obj) }
483 end
484
485 if options[:from] == self and options[:to] == self then
486 raise(ArgumentError, "Need to specify either :from or :to")
487 elsif options[:from] == options[:to] then
488 raise(ArgumentError, "Self-adaption: :from and :to both are " +
489 options[:to].inspect)
490 end
491
492 unless options[:via]
493 raise(ArgumentError, "Need to specify how to adapt (use :via or block)")
494 end
495
496 Contract.adaptions[options[:to]] << options
497 end
498
499 # Built-in adaption routes that Ruby already uses in its C code.
500 adaption :to => Symbol, :via => :to_sym
501 adaption :to => String, :via => :to_str
502 adaption :to => Array, :via => :to_ary
503 adaption :to => Integer, :via => :to_int
504 end
505 # This file contains code for integrating the contract library with built-in
506 # classes and methods.
507 #
508 # See Contract::Check, Module#signature and Module#fulfills.
509
510
511 class Contract < Test::Unit::TestCase
512 # Implements checks that can for example be used in Module#signature
513 # specifications. They are implemented simply by overriding the === case
514 # equality operator. They can also be nested like this:
515 # # Matches something that is an Enumerable and that responds to
516 # # either :to_ary or :to_a.
517 # signature :x, Contract::Check::All[
518 # Enumerable,
519 # Contract::Check::Any[
520 # Contract::Check::Quack[:to_a],
521 # Contract::Check::Quack[:to_ary]
522 # ]
523 # ]
524 module Check
525 # An abstract Base class for Contract::Check classes.
526 # Contains logic for instantation.
527 class Base # :nodoc:
528 class << self
529 alias :[] :new
530 end
531
532 def initialize(*args, &block)
533 @args, @block = args, block
534 end
535 end
536
537 # Checks that the specified block matches.
538 # Example:
539 # signature :x, Contract::Check.block { |arg| arg > 0 }
540 class Block < Base
541 def ===(other)
542 @block.call(other)
543 end
544 end
545 # Short-cut for creating a Contract::Check::Block.
546 def self.block(&block) # :yields: arg
547 Block.new(&block)
548 end
549
550 # Checks that all the specified methods are answered.
551 # Example:
552 # signature :x, Contract::Check::Quack[:to_sym]
553 class Quack < Base
554 def ===(other)
555 @args.all? { |arg| other.respond_to?(arg) }
556 end
557 end
558
559 # Checks that all the specified conditions match.
560 # Example:
561 # signature :x, Contract::Check::All[Array, Enumerable]
562 class All < Base
563 def ===(other)
564 @args.all? { |arg| arg === other }
565 end
566 end
567 # Alias for Contract::Check::All
568 And = All unless defined?(And)
569
570 # Checks that at least one of the specified conditions match.
571 # Example:
572 # signature :x, Contract::Check::Any[String, Symbol]
573 class Any < Base
574 def ===(other)
575 @args.any? { |arg| arg === other }
576 end
577 end
578 # Alias for Contract::Check::Any
579 Or = Any unless defined?(Or)
580
581 # Checks that none of the specified conditions match.
582 # Example:
583 # signature :x, Contract::Check::None[Numeric, Symbol]
584 # signature :x, Contract::Check::Not[Comparable]
585 class None < Base
586 def ===(other)
587 not @args.any? { |arg| arg === other }
588 end
589 end
590 # Alias for Contract::Check::None
591 Not = None unless defined?(Not)
592 end
593
594 class << self
595 # Whether signatures should be checked. By default signatures are checked
596 # only when the application is run in $DEBUG mode. (By specifying the -d
597 # switch on the invocation of Ruby.)
598 #
599 # Note: If you want to change this you need to do so before doing any
600 # Module#signature calls or it will not be applied. It's probably best
601 # set right after requiring the contract library.
602 attr_accessor :check_signatures
603 alias :check_signatures? :check_signatures
604
605 # Whether fulfills should be checked. This is enabled by default.
606 #
607 # Note: If you want to change this you need to do so before doing any
608 # Module#fulfills calls or it will not be applied. It's probably best
609 # set right after requiring the contract library.
610 attr_accessor :check_fulfills
611 alias :check_fulfills? :check_fulfills
612
613 # All adaption routes.
614 attr_accessor :adaptions # :nodoc:
615 end
616 self.check_signatures = $DEBUG if self.check_signatures.nil?
617 self.check_fulfills = true if self.check_fulfills.nil?
618 if self.adaptions.nil? then
619 self.adaptions = Hash.new { |hash, key| hash[key] = Array.new }
620 end
621
622 # Tries to adapt the specified object to the specified type.
623 # Returns the old object if no suitable adaption route was found or if
624 # it already is of the specified type.
625 #
626 # This will only use adaptions where the :to part is equal to the specified
627 # type. No multi-step conversion will be performed.
628 def self.adapt(object, type)
629 return object if type === object
630
631 @adaptions[type].each do |adaption|
632 if adaption[:from] === object and
633 (adaption[:if].nil? or adaption[:if] === object)
634 then
635 result = adaption[:via].call(object)
636 return result if type === result
637 end
638 end
639
640 return object
641 end
642 end
643
644
645 class Module
646 # Checks that the arguments and return value of a method match the specified
647 # signature. Checks are only actually done when Contract.check_signatures is
648 # set to true or if the <code>:no_adaption</code> option is +false+. The
649 # method will return +true+ in case it actually inserted the signature check
650 # logic and +nil+ in case it didn't.
651 #
652 # You will usually specify one type specifier (<code>:any</code> which will
653 # allow anything to appear at that position of the argument list or something
654 # that implements the === case equality operator -- samples are Contracts,
655 # Ranges, Classes, Modules, Regexps, Contract::Checks and so on) per argument.
656 # You can also use objects that implement the +call+ method as type specifiers
657 # which includes Methods and Procs.
658 #
659 # If you don't use the <code>:repeated</code> or <code>:allow_trailing</code>
660 # options the method will take exactly as many arguments as there are type
661 # specifiers which means that <code>signature :a_method</code> enforces
662 # +a_method+ having exactly zero arguments.
663 #
664 # The checks are done by wrapping the type checks around the method.
665 # ArgumentError exceptions will be raised in case the signature contract is
666 # not adhered to by your caller.
667 #
668 # An ArgumentError exception will be raised in case the methods natural
669 # argument list size and the signature you specified via Module.signature are
670 # incompatible. (Note that they don't have to be completely equivalent, you
671 # can still have a method taking zero or more arguments and apply a signature
672 # that limits the actual argument count to three arguments.)
673 #
674 # This method can take quite a few options. Here's a complete list:
675 #
676 # <code>:return</code>::
677 # A return type that the method must comply to. Note that this check (if
678 # failed) will actually raise a StandardError instead of an ArgumentError
679 # because the failure likely lies in the method itself and not in what the
680 # caller did.
681 #
682 # <code>:block</code>::
683 # +true+ or +false+ -- whether the method must take a block or not. So
684 # specifying <code>:block => false</code> enforces that the method is not
685 # allowed to have a block supplied.
686 #
687 # <code>:allow_trailing</code>::
688 # +true+ or +false+ -- whether the argument list may contain trailing,
689 # unchecked arguments.
690 #
691 # <code>:repeated</code>::
692 # An Array that specifies arguments of a method that will be repeated over
693 # and over again at the end of the argument list.
694 #
695 # A good sample of this are Array#values_at which takes zero or or more
696 # Numeric arguments and Enumerable#zip which takes zero or more other
697 # Enumerable arguments.
698 #
699 # Note that the Array that was associated with the <code>:repeated</code>
700 # option must not be empty or an ArgumentError exception will be raised.
701 # If there's just one repeated type you can omit the Array and directly
702 # specify the type identifier.
703 #
704 # <code>:no_adaption</code>::
705 # +true+ or +false+ -- whether no type adaption should be performed.
706 #
707 # Usage:
708 # signature(:to_s) # no arguments
709 # signature(:+, :any) # one argument, type unchecked
710 # signature(:+, Fixnum) # one argument, type Fixnum
711 # signature(:+, NumericContract)
712 # signature(:+, 1 .. 10)
713 # signature(:sqrt, lambda { |arg| arg > 0 })
714 #
715 # signature(:each, :block => true) # has to have block
716 # signature(:to_i, :block => false) # not allowed to have block
717 # signature(:to_i, :result => Fixnum) # return value must be Fixnum
718 # signature(:zip, :allow_trailing => true) # unchecked trailing args
719 # signature(:zip, :repeated => [Enumerable]) # repeated trailing args
720 # signature(:zip, :repeated => Enumerable)
721 # # foo(3, 6, 4, 7) works; foo(5), foo(3, 2) etc. don't
722 # signature(:foo, :repeated => [1..4, 5..9])
723 def signature(method, *args)
724 options = {}
725 signature = args.dup
726 options.update(signature.pop) if signature.last.is_a?(Hash)
727
728 return if not Contract.check_signatures? and options[:no_adaption]
729
730 old_method = instance_method(method)
731 remove_method(method) if instance_methods(false).include?(method.to_s)
732
733 arity = old_method.arity
734 if arity != signature.size and
735 (arity >= 0 or signature.size < ~arity) then
736 raise(ArgumentError, "signature isn't compatible with arity")
737 end
738
739 # Normalizes specifiers to Objects that respond to === so that the run-time
740 # checks only have to deal with that case. Also checks that a specifier is
741 # actually valid.
742 convert_specifier = lambda do |item|
743 # Procs, Methods etc.
744 if item.respond_to?(:call) then
745 Contract::Check.block { |arg| item.call(arg) }
746 # Already okay
747 elsif item.respond_to?(:===) or item == :any then
748 item
749 # Unknown specifier
750 else
751 raise(ArgumentError, "unsupported argument specifier #{item.inspect}")
752 end
753 end
754
755 signature.map!(&convert_specifier)
756
757 if options.include?(:repeated) then
758 options[:repeated] = Array(options[:repeated])
759 if options[:repeated].size == 0 then
760 raise(ArgumentError, "repeated arguments may not be an empty Array")
761 else
762 options[:repeated].map!(&convert_specifier)
763 end
764 end
765
766 if options.include?(:return) then
767 options[:return] = convert_specifier.call(options[:return])
768 end
769
770 # We need to keep around references to our arguments because we will
771 # need to access them via ObjectSpace._id2ref so that they do not
772 # get garbage collected.
773 @signatures ||= Hash.new { |hash, key| hash[key] = Array.new }
774 @signatures[method] << [signature, options, old_method]
775
776 adapted = Proc.new do |obj, type, assign_to|
777 if options[:no_adaption] then
778 obj
779 elsif assign_to then
780 %{(#{assign_to} = Contract.adapt(#{obj}, #{type}))}
781 else
782 %{Contract.adapt(#{obj}, #{type})}
783 end
784 end
785
786 # We have to use class_eval so that signatures can be specified for
787 # methods taking blocks in Ruby 1.8. (This will be obsolete in 1.9)
788 # We also make the checks as efficient as we can.
789 code = %{
790 def #{method}(*args, &block)
791 old_args = args.dup
792
793 #{if options.include?(:block) then
794 if options[:block] then
795 %{raise(ArgumentError, "no block given") unless block}
796 else
797 %{raise(ArgumentError, "block given") if block}
798 end
799 end
800 }
801
802 #{if not(options[:allow_trailing] or options.include?(:repeated))
803 msg = "wrong number of arguments (\#{args.size} for " +
804 "#{signature.size})"
805 %{if args.size != #{signature.size} then
806 raise(ArgumentError, "#{msg}")
807 end
808 }
809 elsif signature.size > 0
810 msg = "wrong number of arguments (\#{args.size} for " +
811 "at least #{signature.size}"
812 %{if args.size < #{signature.size} then
813 raise(ArgumentError, "#{msg}")
814 end
815 }
816 end
817 }
818
819 #{index = 0
820 signature.map do |part|
821 next if part == :any
822 index += 1
823 msg = "argument #{index} (\#{arg.inspect}) does not match " +
824 "#{part.inspect}"
825 %{type = ObjectSpace._id2ref(#{part.object_id})
826 arg = args.shift
827 unless type === #{adapted[%{arg}, %{type}, %{old_args[#{index - 1}]}]}
828 raise(ArgumentError, "#{msg}")
829 end
830 }
831 end
832 }
833
834 #{if repeated = options[:repeated] then
835 msg = "argument \#{idx + #{signature.size}}" +
836 "(\#{arg.inspect}) does not match \#{part.inspect}"
837 %{parts = ObjectSpace._id2ref(#{repeated.object_id})
838 args.each_with_index do |arg, idx|
839 part = parts[idx % #{repeated.size}]
840 if part != :any and
841 not part === (#{adapted[%{arg}, %{part}, %{old_args[idx]}]})
842 then
843 raise(ArgumentError, "#{msg}")
844 end
845 end
846 }
847 end
848 }
849
850 result = ObjectSpace._id2ref(#{old_method.object_id}).bind(self).
851 call(*old_args, &block)
852 #{if rt = options[:return] and rt != :any then
853 msg = "return value (\#{result.inspect}) does not match #{rt.inspect}"
854 %{type = ObjectSpace._id2ref(#{rt.object_id})
855 unless type === #{adapted[%{result}, %{type}]}
856 raise(StandardError, "#{msg}")
857 end
858 }
859 end
860 }
861 end
862 }
863 class_eval code, "(signature check for #{old_method.inspect[/: (.+?)>\Z/, 1]})"
864
865 return true
866 end
867
868 # Specifies that this Module/Class fulfills one or more contracts. The contracts
869 # will automatically be verified after an instance has been successfully created.
870 # This only actually does the checks when Contract.check_fulfills is enabled.
871 # The method will return +true+ in case it actually inserted the check logic and
872 # +nil+ in case it didn't.
873 #
874 # Note that this works by overriding the #initialize method which means that you
875 # should either add the fulfills statements after your initialize method or call
876 # the previously defined initialize method from your new one.
877 def fulfills(*contracts)
878 return unless Contract.check_fulfills?
879
880 contracts.each do |contract|
881 contract.implications.each do |implication|
882 include implication
883 end
884 end
885
886 old_method = instance_method(:initialize)
887 remove_method(:initialize) if instance_methods(false).include?("initialize")
888
889 # Keep visible references around so that the GC will not eat these up.
890 @fulfills ||= Array.new
891 @fulfills << [contracts, old_method]
892
893 # Have to use class_eval because define_method does not allow methods to take
894 # blocks. This can be cleaned up when Ruby 1.9 has become current.
895 class_eval %{
896 def initialize(*args, &block)
897 ObjectSpace._id2ref(#{old_method.object_id}).bind(self).call(*args, &block)
898 ObjectSpace._id2ref(#{contracts.object_id}).each do |contract|
899 contract.enforce self
900 end
901 end
902 }, "(post initialization contract check for #{self.inspect})"
903
904 return true
905 end
906 end
907
908
909 module Kernel
910 # Adds an adaption route from the specified type to the specified type.
911 # Basic usage looks like this:
912 # adaption :from => StringIO, :to => String, :via => :read
913 #
914 # This method takes various options. Here's a complete list:
915 #
916 # <code>:from</code>::
917 # The type that can be converted from. Defaults to +self+ meaning you
918 # can safely omit it in Class, Module or Contract context.
919 #
920 # <code>:to</code>::
921 # The type that can be converted to. Defaults to +self+ meaning you
922 # can safely omit it in Class, Module or Contract context.
923 #
924 # Note that you need to specify either <code>:from</code> or
925 # <code>:to</code>.
926 #
927 # <code>:via</code>::
928 # How the <code>:from</code> type will be converted to the
929 # <code>:to</code> type. If this is a Symbol the conversion will be
930 # done by invoking the method identified by that Symbol on the
931 # source object. Otherwise this should be something that responds to
932 # the +call+ method (for example Methods and Procs) which will get
933 # the source object as its argument and which should return the
934 # target object.
935 #
936 # <code>:if</code>::
937 # The conversion can only be performed if this condition is met.
938 # This can either be something that implements the === case
939 # equivalence operator or something that implements the +call+
940 # method. So Methods, Procs, Modules, Classes and Contracts all
941 # make sense in this context. You can also specify a Symbol in
942 # which case the conversion can only be performed if the source
943 # object responds to the method identified by that Symbol.
944 #
945 # Note that the <code>:if</code> option will default to the same
946 # value as the <code>:via</code> option if the <code>:via</code>
947 # option is a Symbol.
948 #
949 # If you invoke this method with a block it will be used instead of
950 # the <code>:via</code> option.
951 #
952 # See Contract.adapt for how conversion look-ups are performed.
953 def adaption(options = {}, &block) # :yield: source_object
954 options = {
955 :from => self,
956 :to => self
957 }.merge(options)
958
959 if block then
960 if options.include?(:via) then
961 raise(ArgumentError, "Can't use both block and :via")
962 else
963 options[:via] = block
964 end
965 end
966
967 if options[:via].respond_to?(:to_sym) then
968 options[:via] = options[:via].to_sym
969 end
970
971 options[:if] ||= options[:via] if options[:via].is_a?(Symbol)
972
973 if options[:via].is_a?(Symbol) then
974 symbol = options[:via]
975 options[:via] = lambda { |obj| obj.send(symbol) }
976 end
977
978 if options[:if].respond_to?(:to_sym) then
979 options[:if] = options[:if].to_sym
980 end
981
982 if options[:if].is_a?(Symbol) then
983 options[:if] = Contract::Check::Quack[options[:if]]
984 elsif options[:if].respond_to?(:call) then
985 callable = options[:if]
986 options[:if] = Contract::Check.block { |obj| callable.call(obj) }
987 end
988
989 if options[:from] == self and options[:to] == self then
990 raise(ArgumentError, "Need to specify either :from or :to")
991 elsif options[:from] == options[:to] then
992 raise(ArgumentError, "Self-adaption: :from and :to both are " +
993 options[:to].inspect)
994 end
995
996 unless options[:via]
997 raise(ArgumentError, "Need to specify how to adapt (use :via or block)")
998 end
999
1000 Contract.adaptions[options[:to]] << options
1001 end
1002
1003 # Built-in adaption routes that Ruby already uses in its C code.
1004 adaption :to => Symbol, :via => :to_sym
1005 adaption :to => String, :via => :to_str
1006 adaption :to => Array, :via => :to_ary
1007 adaption :to => Integer, :via => :to_int
1008 end