filename./lib/contract/integration.rb
total coverage46.6
code coverage43.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

Valid XHTML 1.1! Valid CSS!