50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
# File 'lib/contracts/decorators.rb', line 50
def common_method_added(name, is_class_method)
decorators = fetch_decorators
return if decorators.empty?
@decorated_methods ||= {:class_methods => {}, :instance_methods => {}}
if is_class_method
method_reference = SingletonMethodReference.new(name, method(name))
method_type = :class_methods
is_private = self.private_methods.include?(name) || self.private_methods.include?(name.to_s)
else
method_reference = MethodReference.new(name, instance_method(name))
method_type = :instance_methods
is_private = self.private_instance_methods.include?(name) || self.private_instance_methods.include?(name.to_s)
end
@decorated_methods[method_type][name] ||= []
pattern_matching = false
decorators.each do |klass, args|
decorator = klass.new(self, method_reference, *args)
@decorated_methods[method_type][name] << decorator
pattern_matching ||= decorator.pattern_match?
end
if @decorated_methods[method_type][name].any? { |x| x.method != method_reference }
@decorated_methods[method_type][name].each do |decorator|
decorator.pattern_match!
end
pattern_matching = true
end
method_reference.make_alias(self)
return if ENV["NO_CONTRACTS"] && !pattern_matching
=begin
Very important: THe line `current = #{self}` in the start is crucial.
Not having it means that any method that used contracts could NOT use `super`
(see this issue for example: https://github.com/egonSchiele/contracts.ruby/issues/27).
Here's why: Suppose you have this code:
class Foo
Contract nil => String
def to_s
"Foo"
end
end
class Bar < Foo
Contract nil => String
def to_s
super + "Bar"
end
end
b = Bar.new
p b.to_s
`to_s` in Bar calls `super`. So you expect this to call `Foo`'s to_s. However,
we have overwritten the function (that's what this next defn is). So it gets a
reference to the function to call by looking at `decorated_methods`.
Now, this line used to read something like:
current = self#{is_class_method ? "" : ".class"}
In that case, `self` would always be `Bar`, regardless of whether you were calling
Foo's to_s or Bar's to_s. So you would keep getting Bar's decorated_methods, which
means you would always call Bar's to_s...infinite recursion! Instead, you want to
call Foo's version of decorated_methods. So the line needs to be `current = #{self}`.
=end
current = self
method_reference.make_definition(self) do |*args, &blk|
ancestors = current.ancestors
ancestors.shift while current && !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
current = ancestors.shift
end
if !current.respond_to?(:decorated_methods) || current.decorated_methods.nil?
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."
end
methods = current.decorated_methods[method_type][name]
success = false
i = 0
result = nil
expected_error = methods[0].failure_exception
while !success
method = methods[i]
i += 1
begin
success = true
result = method.call_with(self, *args, &blk)
rescue expected_error => error
success = false
unless methods[i]
begin
::Contract.failure_callback(error.data, false)
rescue expected_error => final_error
raise final_error.to_contract_error
end
end
end
end
result
end
method_reference.make_private(self) if is_private
end
|