26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
|
# File 'lib/contracts/decorators.rb', line 26
def common_method_added name, is_class_method
return unless @decorators
decorators = @decorators.dup
@decorators = nil
@decorated_methods ||= {:class_methods => {}, :instance_methods => {}}
if is_class_method
method_reference = method(name)
method_type = :class_methods
is_private = self.private_methods.include?(name) || self.private_methods.include?(name.to_s)
else
method_reference = 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] ||= []
decorators.each do |klass, args|
decorator = klass.new(self, method_reference, *args)
@decorated_methods[method_type][name] << decorator
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
end
=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
method_def = %{
def #{is_class_method ? "self." : ""}#{name}(*args, &blk)
current = #{self}
ancestors = current.ancestors
ancestors.shift # first one is just the class itself
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[#{is_class_method ? ":class_methods" : ":instance_methods"}][#{name.inspect}]
# this adds support for overloading methods. Here we go through each method and call it with the arguments.
# If we get a ContractError, we move to the next function. Otherwise we return the result.
# If we run out of functions, we raise the last ContractError.
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
#{is_private ? "private #{name.inspect}" : ""}
}
class_eval method_def, __FILE__, __LINE__ + 1
end
|