/
method.rb
330 lines (264 loc) · 9.01 KB
/
method.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
##
# Method objects are essentially detached, freely passed-around methods. The
# Method is a copy of the method on the object at the time of extraction, so
# if the method itself is changed, overridden, aliased or removed from the
# object, the Method object still contains the old functionality. In addition,
# the call itself is not in any way stored so it will reflect the state of the
# object at the time of calling.
#
# Methods are normally bound to a particular object but it is possible to use
# Method#unbind to create an UnboundMethod object for the purpose of
# re-binding to a different object.
class Method
##
# Takes and stores the receiver object, the method's bytecodes and the
# Module that the method is defined in.
def initialize(receiver, defined_in, executable, name)
@receiver = receiver
@defined_in = defined_in
@executable = executable
@name = name
end
attr_reader :receiver
attr_reader :defined_in
attr_reader :executable
def name
@name
end
##
# Method objects are equal if they have the same body and are bound to the
# same object.
def ==(other)
other.class == Method &&
@receiver.equal?(other.receiver) &&
(@executable == other.executable ||
Rubinius::MethodEquality.method_equal_to_delegated_method_receiver?(self, other))
end
alias_method :eql?, :==
##
# Indication of how many arguments this method takes. It is defined so that
# a non-negative Integer means the method takes that fixed amount of
# arguments (up to 1024 currently.) A negative Integer is used to indicate a
# variable argument count. The number is ((-n) - 1), where n is the number
# of required args. Blocks are not counted.
#
# def foo(); end # arity => 0
# def foo(a, b); end # arity => 2
# def foo(a, &b); end # arity => 1
# def foo(a, b = nil); end # arity => ((-1) -1) => -2
# def foo(*a); end # arity => ((-0) -1) => -1
# def foo(a, b, *c, &d); end # arity => ((-2) -1) => -3
def arity
@executable.arity
end
##
# Execute the method. This works exactly like calling a method with the same
# code on the receiver object. Arguments and a block can be supplied
# optionally.
def call(*args, &block)
@executable.invoke(@name, @defined_in, @receiver, args, block)
end
alias_method :[], :call
##
# String representation of this Method includes the method name, the Module
# it is defined in and the Module that it was extracted from.
def inspect
file, line = source_location()
if @executable.respond_to? :eval_source
if src = @executable.eval_source
src_line = src.split("\n")[line - 1]
return "#<#{self.class}: #{@receiver.class}##{@name} (defined in #{@defined_in} from \"#{src_line}\")>"
end
end
if file
"#<#{self.class}: #{@receiver.class}##{@name} (defined in #{@defined_in} at #{file}:#{line})>"
else
"#<#{self.class}: #{@receiver.class}##{@name} (defined in #{@defined_in})>"
end
end
alias_method :to_s, :inspect
##
# Location gives the file and line number of the start of this method's
# definition.
def source_location
if @executable.respond_to? :file
[@executable.file.to_s, @executable.defined_line]
elsif @executable.respond_to? :source_location
@executable.source_location
else
nil
end
end
def source
if @executable.respond_to? :eval_source
return @executable.eval_source
end
return nil
end
def parameters
if @executable.respond_to? :parameters
@executable.parameters
else
[]
end
end
def owner
if @defined_in.class == Rubinius::IncludedModule
@defined_in.module
else
@defined_in
end
end
##
# Returns a Proc object corresponding to this Method.
def to_proc
Proc.from_method self
end
##
# Calls curry on the method in proc representation
def curry(n = nil)
to_proc.curry(n)
end
##
# Detach this Method from the receiver object it is bound to and create an
# UnboundMethod object. Populates the UnboundMethod with the method data as
# well as the Module it is defined in and the Module it was extracted from.
#
# See UnboundMethod for more information.
def unbind
UnboundMethod.new(@defined_in, @executable, @receiver.class, @name)
end
def super_method
superclass = @defined_in.direct_superclass
if superclass
mod, entry = superclass.lookup_method(@name)
if entry && entry.visibility != :undef
return Method.new(@receiver, superclass, entry.method, @name)
end
end
return nil
end
def for_define_method(name, klass)
Rubinius::Type.bindable_method? self.defined_in, klass
@executable.for_define_method(name, self.unbind)
end
end
##
# UnboundMethods are similar to Method objects except that they are not
# connected to any particular object. They cannot be used standalone for this
# reason, and must be bound to an object first. The object must be kind_of?
# the Module in which this method was originally defined.
#
# UnboundMethods can be created in two ways: first, any existing Method object
# can be sent #unbind to detach it from its current object and return an
# UnboundMethod instead. Secondly, they can be directly created by calling
# Module#instance_method with the desired method's name.
#
# The UnboundMethod is a copy of the method as it existed at the time of
# creation. Any subsequent changes to the original will not affect any
# existing UnboundMethods.
class UnboundMethod
##
# Accepts and stores the Module where the method is defined in as well as
# the CompiledCode itself. Class of the object the method was extracted
# from can be given but will not be stored. This is always used internally
# only.
def initialize(mod, executable, pulled_from, name)
@defined_in = mod
@executable = executable
@pulled_from = pulled_from
@name = name
end
attr_reader :executable
attr_reader :defined_in
def name
@name
end
##
# Convenience method for #binding to the given receiver object and calling
# it with the optionally supplied arguments.
def call_on_instance(obj, *args, &block)
@executable.invoke(@name, @defined_in, obj, args, block)
end
##
# UnboundMethod objects are equal if and only if they refer to the same
# method. One may be an alias for the other or both for a common one. Both
# must have been extracted from the same class or subclass. Two from
# different subclasses will not be considered equal.
def ==(other)
other.kind_of? UnboundMethod and
@defined_in == other.defined_in and
@executable == other.executable
end
def hash
@defined_in.hash ^ @executable.hash
end
##
# See Method#arity.
def arity
@executable.arity
end
##
# Creates a new Method object by attaching this method to the supplied
# receiver. One of the following must be true:
# - the method was defined in a module
# - the method is being bound to an instance of a subclass of the method's source
# - the method is being bound to an instance of the same class
def bind(receiver)
from_module = Rubinius::Type.object_kind_of? @defined_in, Module
from_class = Rubinius::Type.object_kind_of? @defined_in, Class
from_module_not_class = from_module && !from_class
unless Rubinius::Type.object_kind_of?(receiver, @defined_in) or from_module_not_class
if Rubinius::Type.singleton_class_object(@defined_in)
raise TypeError, "illegal attempt to rebind a singleton method to another object"
end
raise TypeError, "Must be bound to an object of kind #{@defined_in}"
end
Method.new receiver, @defined_in, @executable, @name
end
##
# String representation for UnboundMethod includes the method name, the
# Module it is defined in and the Module that it was extracted from.
def inspect
file, line = source_location()
if file
"#<#{self.class}: #{@pulled_from}##{@name} (defined in #{@defined_in} at #{file}:#{line})>"
else
"#<#{self.class}: #{@pulled_from}##{@name} (defined in #{@defined_in})>"
end
end
alias_method :to_s, :inspect
def source_location
if @executable.respond_to? :file
[@executable.file.to_s, @executable.defined_line]
elsif @executable.respond_to? :source_location
@executable.source_location
else
nil
end
end
def parameters
return @executable.respond_to?(:parameters) ? @executable.parameters : []
end
def owner
if @defined_in.class == Rubinius::IncludedModule
@defined_in.module
else
@defined_in
end
end
def super_method
superclass = @defined_in.direct_superclass
if superclass
mod, entry = superclass.lookup_method(@name)
if entry && entry.visibility != :undef
return UnboundMethod.new(superclass, entry.method, @defined_in, @name)
end
end
return nil
end
def for_define_method(name, klass)
Rubinius::Type.bindable_method? self.defined_in, klass
@executable.for_define_method(name, self)
end
end