Re-enable spec/library for full CI runs.
[rbx.git] / kernel / core / method.rb
blob9e823fda58ed672b3be0c045420843e0f3f05239
1 # depends on: class.rb
3 ##
4 # Method objects are essentially detached, freely passed-around methods. The
5 # Method is a copy of the method on the object at the time of extraction, so
6 # if the method itself is changed, overridden, aliased or removed from the
7 # object, the Method object still contains the old functionality. In addition,
8 # the call itself is not in any way stored so it will reflect the state of the
9 # object at the time of calling.
11 # Methods are normally bound to a particular object but it is possible to use
12 # Method#unbind to create an UnboundMethod object for the purpose of
13 # re-binding to a different object.
15 class Method
17   ##
18   # Takes and stores the receiver object, the method's bytecodes and the
19   # Module that the method is defined in.
21   def initialize(receiver, defined_in, compiled_method)
22     @receiver         = receiver
23     @pulled_from      = receiver.__class__
24     @defined_in       = defined_in
25     @compiled_method  = compiled_method
26   end
28   attr_reader :receiver
29   attr_reader :pulled_from
30   attr_reader :defined_in
31   attr_reader :compiled_method
32   protected   :receiver
33   protected   :pulled_from
35   ##
36   # Method objects are equal if they have the same body and are bound to the
37   # same object.
39   def ==(other)
40     return true if other.class == Method and
41                    @receiver.equal?(other.receiver) and
42                    @compiled_method == other.compiled_method
44     false
45   end
47   ##
48   # Indication of how many arguments this method takes. It is defined so that
49   # a non-negative Integer means the method takes that fixed amount of
50   # arguments (up to 1024 currently.) A negative Integer is used to indicate a
51   # variable argument count. The number is ((-n) - 1), where n is the number
52   # of required args. Blocks are not counted.
53   #
54   #   def foo();             end   # arity => 0
55   #   def foo(a, b);         end   # arity => 2
56   #   def foo(a, &b);        end   # arity => 1
57   #   def foo(a, b = nil);   end   # arity => ((-1) -1) => -2
58   #   def foo(*a);           end   # arity => ((-0) -1) => -1
59   #   def foo(a, b, *c, &d); end   # arity => ((-2) -1) => -3
61   def arity()
62     @compiled_method.required
63   end
65   ##
66   # Execute the method. This works exactly like calling a method with the same
67   # code on the receiver object. Arguments and a block can be supplied
68   # optionally.
70   def call(*args, &block)
71     @compiled_method.activate(@receiver, @defined_in, args, &block)
72   end
74   alias_method :[], :call
76   ##
77   # String representation of this Method includes the method name, the Module
78   # it is defined in and the Module that it was extracted from.
80   def inspect()
81     "#<#{self.class}: #{@pulled_from}##{@compiled_method.name} (defined in #{@defined_in})>"
82   end
84   alias_method :to_s, :inspect
86   ##
87   # Location gives the file and line number of the start of this method's
88   # definition.
90   def location()
91     "#{@compiled_method.file}, near line #{@compiled_method.first_line}"
92   end
94   ##
95   # Returns a Proc object corresponding to this Method.
97   def to_proc()
98     env = Method::AsBlockEnvironment.new self
99     Proc.__from_block__(env)
100   end
102   ##
103   # Detach this Method from the receiver object it is bound to and create an
104   # UnboundMethod object. Populates the UnboundMethod with the method data as
105   # well as the Module it is defined in and the Module it was extracted from.
106   #
107   # See UnboundMethod for more information.
109   def unbind()
110     UnboundMethod.new(@defined_in, @compiled_method, @pulled_from)
111   end
116 # Wraps the Method into a BlockEnvironment, for use with Method#to_proc.
118 class Method::AsBlockEnvironment < BlockEnvironment
119   def initialize(method)
120     @method = method
121   end
122   def method; @method.compiled_method; end
123   def file; method.file; end
124   def line; method.first_line; end
125   def redirect_to(obj)
126     @method = @method.unbind.bind(obj)
127   end
128   def call(*args); @method.call(*args); end
129   def call_on_instance(obj, *args)
130     redirect_to(obj).call(*args)
131   end
132   def arity; @method.arity; end
136 # UnboundMethods are similar to Method objects except that they are not
137 # connected to any particular object. They cannot be used standalone for this
138 # reason, and must be bound to an object first. The object must be kind_of?
139 # the Module in which this method was originally defined.
141 # UnboundMethods can be created in two ways: first, any existing Method object
142 # can be sent #unbind to detach it from its current object and return an
143 # UnboundMethod instead. Secondly, they can be directly created by calling
144 # Module#instance_method with the desired method's name.
146 # The UnboundMethod is a copy of the method as it existed at the time of
147 # creation. Any subsequent changes to the original will not affect any
148 # existing UnboundMethods.
150 class UnboundMethod
152   ##
153   # Accepts and stores the Module where the method is defined in as well as
154   # the CompiledMethod itself. Class of the object the method was extracted
155   # from can be given but will not be stored. This is always used internally
156   # only.
158   def initialize(mod, compiled_method, pulled_from = nil)
159     @defined_in       = mod
160     @compiled_method  = compiled_method
161     @pulled_from      = pulled_from
162   end
164   attr_reader :compiled_method
165   attr_reader :defined_in
167   ##
168   # UnboundMethod objects are equal if and only if they refer to the same
169   # method. One may be an alias for the other or both for a common one. Both
170   # must have been extracted from the same class or subclass. Two from
171   # different subclasses will not be considered equal.
173   def ==(other)
174     return true if other.kind_of? UnboundMethod and
175                    @defined_in == other.defined_in and
176                    @compiled_method == other.compiled_method
178     false
179   end
181   ##
182   # See Method#arity.
184   def arity()
185     @compiled_method.required
186   end
188   ##
189   # Creates a new Method object by attaching this method to the supplied
190   # receiver. The receiver must be kind_of? the Module that the method in
191   # question is defined in.
192   #
193   # Notably, this is a difference from MRI which requires that the object is
194   # of the exact Module the method was extracted from. This is safe because
195   # any overridden method will be identified as being defined in a different
196   # Module anyway.
198   def bind(receiver)
199     if @defined_in.kind_of? MetaClass
200       unless @defined_in.attached_instance == receiver
201         raise TypeError, "Must be bound to #{@defined_in.attached_instance.inspect} only"
202       end
203     else
204       unless receiver.__kind_of__ @defined_in
205         raise TypeError, "Must be bound to an object of kind #{@defined_in}"
206       end
207     end
208     Method.new receiver, @defined_in, @compiled_method
209   end
211   ##
212   # Convenience method for #binding to the given receiver object and calling
213   # it with the optionally supplied arguments.
215   def call_on_instance(obj, *args)
216     bind(obj).call(*args)
217   end
219   ##
220   # String representation for UnboundMethod includes the method name, the
221   # Module it is defined in and the Module that it was extracted from.
223   def inspect()
224     "#<#{self.class}: #{@pulled_from}##{@compiled_method.name} (defined in #{@defined_in})>"
225   end
227   alias_method :to_s, :inspect