contracts 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -352,4 +352,11 @@ module Contracts
352
352
  ('a'..'z').to_a.shuffle[0, 10].join
353
353
  end
354
354
  end
355
+
356
+ class Func < CallableClass
357
+ attr_reader :contracts
358
+ def initialize(*contracts)
359
+ @contracts = contracts
360
+ end
361
+ end
355
362
  end
data/lib/contracts.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'decorators'
2
2
  require 'builtin_contracts'
3
-
4
3
  module Contracts
5
4
  def self.included(base)
6
5
  base.extend MethodDecorators
@@ -40,11 +39,11 @@ class Contract < Decorator
40
39
  file, line = data[:method].source_location
41
40
  position = file + ":" + line.to_s
42
41
  end
43
-
42
+ method_name = data[:method].is_a?(Proc) ? "Proc" : data[:method].name
44
43
  %{Contract violation:
45
44
  Expected: #{expected},
46
45
  Actual: #{data[:arg].inspect}
47
- Value guarded in: #{data[:class]}::#{data[:method].name}
46
+ Value guarded in: #{data[:class]}::#{method_name}
48
47
  With Contract: #{data[:contracts].map { |t| t.is_a?(Class) ? t.name : t.class.name }.join(", ") }
49
48
  At: #{position} }
50
49
  end
@@ -99,6 +98,8 @@ class Contract < Decorator
99
98
  validate_hash(arg, contract)
100
99
  when Contracts::Args
101
100
  valid? arg, contract.contract
101
+ when Func
102
+ arg.is_a?(Method) || arg.is_a?(Proc)
102
103
  else
103
104
  if contract.respond_to? :valid?
104
105
  mkerror(contract.valid?(arg), arg, contract)
@@ -108,22 +109,41 @@ class Contract < Decorator
108
109
  end
109
110
  end
110
111
 
111
- def call(this, *args, &blk)
112
+ def call(*args, &blk)
113
+ call_with(nil, *args, &blk)
114
+ end
115
+
116
+ def call_with(this, *args, &blk)
112
117
  _args = blk ? args + [blk] : args
113
118
  if _args.size != @contracts.size - 1
114
119
  # so it's not *args
115
- if !@contracts[-2].is_a? Contracts::Args
120
+ unless @contracts.any? { |contract| contract.is_a? Contracts::Args }
116
121
  raise %{The number of arguments doesn't match the number of contracts.
117
122
  Did you forget to write a contract for the return value of the function?
118
123
  Or if you want a variable number of arguments using *args, use the Args contract.
119
124
  Args: #{args.inspect}
120
125
  Contracts: #{@contracts.map { |t| t.is_a?(Class) ? t.name : t.class.name }.join(", ")}}
121
126
  end
122
- end
127
+ end
128
+
123
129
  res = Contract.validate_all(_args, @contracts[0, @contracts.size - 1], @klass, @method)
124
130
  return if res == false
125
131
 
126
- result = @method.bind(this).call(*args, &blk)
132
+ # contracts on methods
133
+
134
+ contracts.each_with_index do |contract, i|
135
+ if contract.is_a? Func
136
+ args[i] = Contract.new(@klass, args[i], *contract.contracts)
137
+ end
138
+ end
139
+
140
+ if @method.respond_to? :bind
141
+ # instance method
142
+ result = @method.bind(this).call(*args, &blk)
143
+ else
144
+ # class method
145
+ result = @method.call(*args, &blk)
146
+ end
127
147
 
128
148
  if args.size == @contracts.size - 1
129
149
  Contract.validate(result, @contracts[-1], @klass, @method, @contracts)
@@ -161,18 +181,25 @@ Contracts: #{@contracts.map { |t| t.is_a?(Class) ? t.name : t.class.name }.join(
161
181
  mkerror(valid, arg, contract)
162
182
  end
163
183
 
164
- def self.validate_all(args, contracts, klass, method)
165
- # we assume that any mismatch in # of args/contracts
184
+ def self.validate_all(params, contracts, klass, method)
185
+ # we assume that any mismatch in # of params/contracts
166
186
  # has been checked befoer this point.
167
- if args.size != contracts.size
168
- # assumed: contracts[-1].is_a? Args
169
- while contracts.size < args.size
170
- contracts << contracts[-1].dup
187
+ args_index = contracts.index do |contract|
188
+ contract.is_a? Contracts::Args
189
+ end
190
+ if args_index
191
+ # there is a *args at this index.
192
+ # Now we need to see how many arguments this contract
193
+ # accounts for and just duplicate the contract for all
194
+ # of those args.
195
+ args_contract = contracts[args_index]
196
+ while contracts.size < params.size
197
+ contracts.insert(args_index, args_contract.dup)
171
198
  end
172
199
  end
173
200
 
174
- args.zip(contracts).each do |arg, contract|
175
- result = validate(arg, contract, klass, method, contracts)
201
+ params.zip(contracts).each do |param, contract|
202
+ result = validate(param, contract, klass, method, contracts)
176
203
  return result if result == false
177
204
  end
178
205
  end
data/lib/decorators.rb CHANGED
@@ -11,6 +11,16 @@ module MethodDecorators
11
11
  # to find the decorator for that method. This is how we associate decorators
12
12
  # with methods.
13
13
  def method_added(name)
14
+ common_method_added name, false
15
+ super
16
+ end
17
+
18
+ def singleton_method_added name
19
+ common_method_added name, true
20
+ super
21
+ end
22
+
23
+ def common_method_added name, is_class_method
14
24
  return unless @decorators
15
25
 
16
26
  decorators = @decorators.dup
@@ -24,7 +34,15 @@ module MethodDecorators
24
34
  # a reference to the method gets passed into the contract here. This is good because
25
35
  # we are going to redefine this method with a new name below...so this reference is
26
36
  # now the *only* reference to the old method that exists.
27
- decorator = klass.respond_to?(:new) ? klass.new(self, instance_method(name), *args) : klass
37
+ if klass.respond_to? :new
38
+ if is_class_method
39
+ decorator = klass.new(self, method(name), *args)
40
+ else
41
+ decorator = klass.new(self, instance_method(name), *args)
42
+ end
43
+ else
44
+ decorator = klass
45
+ end
28
46
  @decorated_methods[name] << decorator
29
47
  end
30
48
 
@@ -33,16 +51,15 @@ module MethodDecorators
33
51
  # The decorator in turn has a reference to the actual method, so it can call it
34
52
  # on its own, after doing it's decorating of course.
35
53
  class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
36
- def #{name}(*args, &blk)
54
+ def #{is_class_method ? "self." : ""}#{name}(*args, &blk)
37
55
  ret = nil
38
- self.class.decorated_methods[#{name.inspect}].each do |decorator|
39
- ret = decorator.call(self, *args, &blk)
56
+ self.#{is_class_method ? "" : "class."}decorated_methods[#{name.inspect}].each do |decorator|
57
+ ret = decorator.call_with(self, *args, &blk)
40
58
  end
41
59
  ret
42
60
  end
43
61
  ruby_eval
44
- super
45
- end
62
+ end
46
63
 
47
64
  def decorate(klass, *args)
48
65
  @decorators ||= []
data/lib/foo.rb CHANGED
@@ -1,10 +1,15 @@
1
1
  require 'contracts'
2
2
  use_contracts(self)
3
3
 
4
- Contract [Num, String]
5
- def mult
6
- return 1, 2
4
+ add = Proc.new do |x|
5
+ 1
7
6
  end
8
7
 
9
- f = mult
10
- p f
8
+ Contract ArrayOf[Num], Func[String, Num], ArrayOf[Num]
9
+ def map nums, func
10
+ nums.map do |num|
11
+ func.call(num)
12
+ end
13
+ end
14
+
15
+ p map([1, 2, 3], add)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contracts
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 7
10
- version: 0.0.7
9
+ - 8
10
+ version: 0.0.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Aditya Bhargava