contracts 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 77eb6f2fcb9b44586ae85de49add3fff7e1ece96
4
- data.tar.gz: 43d47be3ae6f1b13b997dde2ba6701b5578f84a8
3
+ metadata.gz: 2aee00ef1803ee1ac8e84e96156494f7b7c4162a
4
+ data.tar.gz: a3798c342c9b29170f523f23083baeb2553cbd83
5
5
  SHA512:
6
- metadata.gz: adaf871d3c827fcfad8b1ea3731d34ddc5023ab4d952a668c797e89bff7c798c1464db1acf80ae1eb446867ba817e56fc3be0c9401dd3e35d6aedea54db69c52
7
- data.tar.gz: 297cc7359bf60acac92450b4f2c1a96bae8e845dedf3f088b01f86e7271f2d312d4449d2c908a5ebacadd46f1f9f2031a4ee5eccd8b35bf35d3420d5b95a821c
6
+ metadata.gz: d1404d6dd2fe5212b9c7a8bfa682b982bf612161200e33bc14ab73609eaac0681b85540a182a3d4d2437d3446380241785d229b6a8145c5a8c3fda8e37f682f5
7
+ data.tar.gz: 0912d5946702185218f94d1058577e92fd9f7b668e248ab4d796e67b620455f4a43e2a0e02d4871f8def9c8666feb3f4343c4f3f6d04d6c1a25b97541c7afd27
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contracts (0.4)
4
+ contracts (0.5)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
data/README.md CHANGED
@@ -73,3 +73,4 @@ Copyright 2012 [Aditya Bhargava](http://adit.io).
73
73
  Major improvements by [Alexey Fedorov](https://github.com/waterlink).
74
74
 
75
75
  BSD Licensed.
76
+
@@ -405,7 +405,9 @@ If you want to disable contracts, set the `NO_CONTRACTS` environment variable. T
405
405
 
406
406
  ## Method overloading
407
407
 
408
- You can use contracts for method overloading! For example, here's a factorial function without method overloading:
408
+ You can use contracts for method overloading! This is commonly called "pattern matching" in functional programming languages.
409
+
410
+ For example, here's a factorial function without method overloading:
409
411
 
410
412
  ```ruby
411
413
  Contract Num => Num
@@ -434,6 +436,45 @@ end
434
436
 
435
437
  For an argument, each function will be tried in order. The first function that doesn't raise a `ContractError` will be used. So in this case, if x == 1, the first function will be used. For all other values, the second function will be used.
436
438
 
439
+ This allows you write methods more declaratively, rather than using conditional branching. This feature is not only useful for recursion; you can use it to keep parallel use cases separate:
440
+
441
+ ```ruby
442
+ Contract And[Num, lambda{|n| n < 12 }] => Ticket
443
+ def get_ticket(age)
444
+ ChildTicket.new(age: age)
445
+ end
446
+
447
+ Contract And[Num, lambda{|n| n >= 12 }] => Ticket
448
+ def get_ticket(age)
449
+ AdultTicket.new(age: age)
450
+ end
451
+
452
+ ```
453
+
454
+ Note that the second `get_ticket` contract above could have been simplified to:
455
+
456
+ ```ruby
457
+ Contract Num => Ticket
458
+ ```
459
+
460
+ This is because the first contract eliminated the possibility of `age` being less than 12. However, the simpler contract is less explicit; you may want to "spell out" the age condition for clarity, especially if the method is overloaded with many contracts.
461
+
462
+ ## Contracts in modules
463
+
464
+ To use contracts on module you need to include both `Contracts` and `Contracts::Modules` into it:
465
+
466
+ ```ruby
467
+ module M
468
+ include Contracts
469
+ include Contracts::Modules
470
+
471
+ Contract String => String
472
+ def self.parse
473
+ # do some hard parsing
474
+ end
475
+ end
476
+ ```
477
+
437
478
  ## Invariants
438
479
 
439
480
  Invariants are conditions on objects that should always hold. If after any method call on given object, any of the Invariants fails, then Invariant violation error will be generated.
@@ -34,7 +34,7 @@ def benchmark
34
34
  1000000.times do |_|
35
35
  contracts_add(rand(1000), rand(1000))
36
36
  end
37
- end
37
+ end
38
38
  end
39
39
  end
40
40
 
@@ -48,12 +48,12 @@ def profile
48
48
  profilers << MethodProfiler.observe(UnboundMethod)
49
49
  10000.times do |_|
50
50
  contracts_add(rand(1000), rand(1000))
51
- end
51
+ end
52
52
  profilers.each { |p| puts p.report }
53
53
  end
54
54
 
55
55
  def ruby_prof
56
- RubyProf.start
56
+ RubyProf.start
57
57
  100000.times do |_|
58
58
  contracts_add(rand(1000), rand(1000))
59
59
  end
@@ -3,7 +3,6 @@ require File.expand_path(File.join(__FILE__, '../lib/contracts/version'))
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "contracts"
5
5
  s.version = Contracts::VERSION
6
- s.date = "2014-05-08"
7
6
  s.summary = "Contracts for Ruby."
8
7
  s.description = "This library provides contracts for Ruby. Contracts let you clearly express how your code behaves, and free you from writing tons of boilerplate, defensive code."
9
8
  s.author = "Aditya Bhargava"
@@ -1,54 +1,29 @@
1
+ require 'contracts/core_ext'
1
2
  require 'contracts/support'
3
+ require 'contracts/method_reference'
4
+ require 'contracts/errors'
2
5
  require 'contracts/decorators'
6
+ require 'contracts/eigenclass'
3
7
  require 'contracts/builtin_contracts'
8
+ require 'contracts/modules'
4
9
  require 'contracts/invariants'
5
10
 
6
- # @private
7
- # Base class for Contract errors
8
- #
9
- # If default failure callback is used it stores failure data
10
- class ContractBaseError < ArgumentError
11
- attr_reader :data
12
-
13
- def initialize(message, data)
14
- super(message)
15
- @data = data
16
- end
17
-
18
- # Used to convert to simple ContractError from other contract errors
19
- def to_contract_error
20
- self
21
- end
22
- end
23
-
24
- # Default contract error
25
- #
26
- # If default failure callback is used, users normally see only these contract errors
27
- class ContractError < ContractBaseError
28
- end
29
-
30
- # @private
31
- # Special contract error used internally to detect pattern failure during pattern matching
32
- class PatternMatchingError < ContractBaseError
33
- # Used to convert to ContractError from PatternMatchingError
34
- def to_contract_error
35
- ContractError.new(to_s, data)
36
- end
37
- end
38
-
39
11
  module Contracts
40
12
  def self.included(base)
41
- common base
13
+ common(base)
42
14
  end
43
15
 
44
16
  def self.extended(base)
45
- common base
17
+ common(base)
46
18
  end
47
19
 
48
- def self.common base
20
+ def self.common(base)
21
+ Eigenclass.lift(base)
22
+
49
23
  return if base.respond_to?(:Contract)
50
24
 
51
- base.extend MethodDecorators
25
+ base.extend(MethodDecorators)
26
+
52
27
  base.instance_eval do
53
28
  def functype(funcname)
54
29
  contracts = self.decorated_methods[:class_methods][funcname]
@@ -59,10 +34,13 @@ module Contracts
59
34
  end
60
35
  end
61
36
  end
37
+
62
38
  base.class_eval do
63
- def Contract(*args)
64
- return if ENV["NO_CONTRACTS"]
65
- self.class.Contract(*args)
39
+ unless base.instance_of?(Module)
40
+ def Contract(*args)
41
+ return if ENV["NO_CONTRACTS"]
42
+ self.class.Contract(*args)
43
+ end
66
44
  end
67
45
 
68
46
  def functype(funcname)
@@ -131,8 +109,8 @@ class Contract < Contracts::Decorator
131
109
  data[:contract].to_s
132
110
  end
133
111
 
134
- position = Support.method_position(data[:method])
135
- method_name = Support.method_name(data[:method])
112
+ position = Contracts::Support.method_position(data[:method])
113
+ method_name = Contracts::Support.method_name(data[:method])
136
114
 
137
115
  header = if data[:return_value]
138
116
  "Contract violation for return value:"
@@ -261,39 +239,72 @@ class Contract < Contracts::Decorator
261
239
  end
262
240
 
263
241
  def call_with(this, *args, &blk)
264
-
265
242
  _args = blk ? args + [blk] : args
266
243
 
244
+ size = @args_contracts.size
245
+
246
+ last_contract = @args_contracts.last
247
+ proc_present = (Contracts::Func === last_contract ||
248
+ (Class === last_contract && (last_contract <= Proc || last_contract <= Method)))
249
+
250
+ _splat_index = proc_present ? -2 : -1
251
+ splat_present = Contracts::Args === @args_contracts[_splat_index]
252
+ splat_index = size + _splat_index
253
+ splat_index += 1 unless splat_present
254
+
255
+ # Explicitly append blk=nil if nil != Proc contract violation
256
+ # anticipated
257
+ if proc_present && !blk && (splat_present || _args.size < size)
258
+ _args << nil
259
+ end
260
+
261
+ # Size of our iteration is either count of arguments or count of
262
+ # contracts. Without splat - count of arguments, with splat -
263
+ # min(count of contracts, count of arguments)
264
+ _size = _args.size
265
+ iteration_size = _size
266
+ iteration_size = size if !splat_present && size < _size
267
+
267
268
  # check contracts on arguments
268
269
  # fun fact! This is significantly faster than .zip (3.7 secs vs 4.7 secs). Why??
269
- last_index = @args_validators.size - 1
270
+
270
271
  # times is faster than (0..args.size).each
271
- _args.size.times do |i|
272
+ iteration_size.times do |i|
272
273
  # this is done to account for extra args (for *args)
273
- j = i < last_index ? i : last_index
274
+ j = i > splat_index ? splat_index : i
275
+ j = size - 1 if i == _size - 1 && proc_present
274
276
  #unless true #@args_contracts[i].valid?(args[i])
275
277
  unless @args_validators[j][_args[i]]
276
- call_function = Contract.failure_callback({:arg => _args[i], :contract => @args_contracts[j], :class => @klass, :method => @method, :contracts => self, :arg_pos => i+1, :total_args => _args.size})
278
+ call_function = Contract.failure_callback({:arg => _args[i], :contract => @args_contracts[j], :class => @klass, :method => @method, :contracts => self, :arg_pos => i+1, :total_args => _size})
277
279
  return unless call_function
278
280
  end
279
281
  end
280
282
 
281
283
  if @has_func_contracts
282
284
  # contracts on methods
285
+ contracts_size = @args_contracts.size
283
286
  @args_contracts.each_with_index do |contract, i|
284
- if contract.is_a? Contracts::Func
285
- args[i] = Contract.new(@klass, args[i], *contract.contracts)
287
+ next if contracts_size - 1 == i && proc_present && blk
288
+
289
+ if contract.is_a?(Contracts::Func)
290
+ args[i] = Contract.new(@klass, args[i], *contract.contracts)
286
291
  end
287
292
  end
293
+
294
+ if proc_present && blk && last_contract.is_a?(Contracts::Func)
295
+ blk_contract = Contract.new(@klass, blk, *last_contract.contracts)
296
+ blk = Proc.new { |*args, &blk| blk_contract.call(*args, &blk) }
297
+ end
288
298
  end
289
299
 
290
- result = if @method.respond_to? :bind
291
- # instance method
292
- @method.bind(this).call(*args, &blk)
293
- else
294
- # class method
300
+ result = if @method.respond_to?(:call)
301
+ # proc, block, lambda, etc
295
302
  @method.call(*args, &blk)
303
+ else
304
+ # original method name referrence
305
+ @method.send_to(this, *args, &blk)
296
306
  end
307
+
297
308
  unless @ret_validator[result]
298
309
  Contract.failure_callback({:arg => result, :contract => @ret_contract, :class => @klass, :method => @method, :contracts => self, :return_value => true})
299
310
  end
@@ -0,0 +1,15 @@
1
+ class Module
2
+ unless Object.respond_to?(:singleton_class)
3
+ # Compatibility with ruby 1.8
4
+ def singleton_class
5
+ class << self; self; end
6
+ end
7
+ end
8
+
9
+ unless Object.respond_to?(:singleton_class?)
10
+ # Compatibility with ruby 1.8
11
+ def singleton_class?
12
+ self <= Object.singleton_class
13
+ end
14
+ end
15
+ end
@@ -8,6 +8,22 @@ module Contracts
8
8
  end
9
9
  end
10
10
 
11
+ module EigenclassWithOwner
12
+ def self.lift(eigenclass)
13
+ unless with_owner?(eigenclass)
14
+ raise Contracts::ContractsNotIncluded
15
+ end
16
+
17
+ eigenclass
18
+ end
19
+
20
+ private
21
+
22
+ def self.with_owner?(eigenclass)
23
+ eigenclass.respond_to?(:owner_class) && eigenclass.owner_class
24
+ end
25
+ end
26
+
11
27
  # first, when you write a contract, the decorate method gets called which
12
28
  # sets the @decorators variable. Then when the next method after the contract
13
29
  # is defined, method_added is called and we look at the @decorators variable
@@ -18,25 +34,32 @@ module Contracts
18
34
  super
19
35
  end
20
36
 
21
- def singleton_method_added name
37
+ def singleton_method_added(name)
22
38
  common_method_added name, true
23
39
  super
24
40
  end
25
41
 
26
- def common_method_added name, is_class_method
27
- return unless @decorators
42
+ def pop_decorators
43
+ Array(@decorators).tap { @decorators = nil }
44
+ end
45
+
46
+ def fetch_decorators
47
+ pop_decorators + Eigenclass.lift(self).pop_decorators
48
+ end
49
+
50
+ def common_method_added(name, is_class_method)
51
+ decorators = fetch_decorators
52
+ return if decorators.empty?
28
53
 
29
- decorators = @decorators.dup
30
- @decorators = nil
31
54
  @decorated_methods ||= {:class_methods => {}, :instance_methods => {}}
32
55
 
33
56
  if is_class_method
34
- method_reference = method(name)
57
+ method_reference = SingletonMethodReference.new(name, method(name))
35
58
  method_type = :class_methods
36
59
  # private_methods is an array of strings on 1.8 and an array of symbols on 1.9
37
60
  is_private = self.private_methods.include?(name) || self.private_methods.include?(name.to_s)
38
61
  else
39
- method_reference = instance_method(name)
62
+ method_reference = MethodReference.new(name, instance_method(name))
40
63
  method_type = :instance_methods
41
64
  # private_instance_methods is an array of strings on 1.8 and an array of symbols on 1.9
42
65
  is_private = self.private_instance_methods.include?(name) || self.private_instance_methods.include?(name.to_s)
@@ -44,6 +67,7 @@ module Contracts
44
67
 
45
68
  @decorated_methods[method_type][name] ||= []
46
69
 
70
+ pattern_matching = false
47
71
  decorators.each do |klass, args|
48
72
  # a reference to the method gets passed into the contract here. This is good because
49
73
  # we are going to redefine this method with a new name below...so this reference is
@@ -51,14 +75,21 @@ module Contracts
51
75
  # We assume here that the decorator (klass) responds to .new
52
76
  decorator = klass.new(self, method_reference, *args)
53
77
  @decorated_methods[method_type][name] << decorator
78
+ pattern_matching ||= decorator.pattern_match?
54
79
  end
55
80
 
56
81
  if @decorated_methods[method_type][name].any? { |x| x.method != method_reference }
57
82
  @decorated_methods[method_type][name].each do |decorator|
58
83
  decorator.pattern_match!
59
84
  end
85
+
86
+ pattern_matching = true
60
87
  end
61
88
 
89
+ method_reference.make_alias(self)
90
+
91
+ return if ENV["NO_CONTRACTS"] && !pattern_matching
92
+
62
93
  # in place of this method, we are going to define our own method. This method
63
94
  # just calls the decorator passing in all args that were to be passed into the method.
64
95
  # The decorator in turn has a reference to the actual method, so it can call it
@@ -100,52 +131,54 @@ Here's why: Suppose you have this code:
100
131
  means you would always call Bar's to_s...infinite recursion! Instead, you want to
101
132
  call Foo's version of decorated_methods. So the line needs to be `current = #{self}`.
102
133
  =end
103
- method_def = %{
104
- def #{is_class_method ? "self." : ""}#{name}(*args, &blk)
105
- current = #{self}
106
- ancestors = current.ancestors
107
- ancestors.shift # first one is just the class itself
108
- while current && !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
109
- current = ancestors.shift
110
- end
111
- if !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
112
- raise "Couldn't find decorator for method " + self.class.name + ":#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
113
- end
114
- methods = current.decorated_methods[#{is_class_method ? ":class_methods" : ":instance_methods"}][#{name.inspect}]
115
-
116
- # this adds support for overloading methods. Here we go through each method and call it with the arguments.
117
- # If we get a ContractError, we move to the next function. Otherwise we return the result.
118
- # If we run out of functions, we raise the last ContractError.
119
- success = false
120
- i = 0
121
- result = nil
122
- expected_error = methods[0].failure_exception
123
- while !success
124
- method = methods[i]
125
- i += 1
126
- begin
127
- success = true
128
- result = method.call_with(self, *args, &blk)
129
- rescue expected_error => error
130
- success = false
131
- unless methods[i]
132
- begin
133
- ::Contract.failure_callback(error.data, false)
134
- rescue expected_error => final_error
135
- raise final_error.to_contract_error
136
- end
134
+
135
+ current = self
136
+ method_reference.make_definition(self) do |*args, &blk|
137
+ ancestors = current.ancestors
138
+ ancestors.shift # first one is just the class itself
139
+ while current && !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
140
+ current = ancestors.shift
141
+ end
142
+ if !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
143
+ raise "Couldn't find decorator for method " + self.class.name + ":#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
144
+ end
145
+ methods = current.decorated_methods[method_type][name]
146
+
147
+ # this adds support for overloading methods. Here we go through each method and call it with the arguments.
148
+ # If we get a ContractError, we move to the next function. Otherwise we return the result.
149
+ # If we run out of functions, we raise the last ContractError.
150
+ success = false
151
+ i = 0
152
+ result = nil
153
+ expected_error = methods[0].failure_exception
154
+ while !success
155
+ method = methods[i]
156
+ i += 1
157
+ begin
158
+ success = true
159
+ result = method.call_with(self, *args, &blk)
160
+ rescue expected_error => error
161
+ success = false
162
+ unless methods[i]
163
+ begin
164
+ ::Contract.failure_callback(error.data, false)
165
+ rescue expected_error => final_error
166
+ raise final_error.to_contract_error
137
167
  end
138
168
  end
139
169
  end
140
- result
141
170
  end
142
- #{is_private ? "private #{name.inspect}" : ""}
143
- }
171
+ result
172
+ end
144
173
 
145
- class_eval method_def, __FILE__, __LINE__ + 1
174
+ method_reference.make_private(self) if is_private
146
175
  end
147
176
 
148
177
  def decorate(klass, *args)
178
+ if self.singleton_class?
179
+ return EigenclassWithOwner.lift(self).owner_class.decorate(klass, *args)
180
+ end
181
+
149
182
  @decorators ||= []
150
183
  @decorators << [klass, args]
151
184
  end
@@ -166,7 +199,6 @@ Here's why: Suppose you have this code:
166
199
  # inside, `decorate` is called with those params.
167
200
  MethodDecorators.module_eval <<-ruby_eval, __FILE__, __LINE__ + 1
168
201
  def #{klass}(*args, &blk)
169
- return if ENV["NO_CONTRACTS"]
170
202
  decorate(#{klass}, *args, &blk)
171
203
  end
172
204
  ruby_eval