Temporary tag for this failure. Updated CI spec coming.
[rbx.git] / kernel / core / method.rb
blobf7563b6793c08184f8cce98d2525b5e1967e8f27
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_environment(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
115 class Method::AsBlockEnvironment < BlockEnvironment
116   def initialize(method)
117     @method = method
118   end
119   def method; @method.compiled_method; end
120   def file; method.file; end
121   def line; method.first_line; end
122   def redirect_to(obj)
123     @method = @method.unbind.bind(obj)
124   end
125   def call(*args); @method.call(*args); end
126   def call_on_instance(obj, *args)
127     redirect_to(obj).call(*args)
128   end
129   def arity; @method.arity; end
133 # UnboundMethods are similar to Method objects except that they are not
134 # connected to any particular object. They cannot be used standalone for this
135 # reason, and must be bound to an object first. The object must be kind_of?
136 # the Module in which this method was originally defined.
138 # UnboundMethods can be created in two ways: first, any existing Method object
139 # can be sent #unbind to detach it from its current object and return an
140 # UnboundMethod instead. Secondly, they can be directly created by calling
141 # Module#instance_method with the desired method's name.
143 # The UnboundMethod is a copy of the method as it existed at the time of
144 # creation. Any subsequent changes to the original will not affect any
145 # existing UnboundMethods.
147 class UnboundMethod
149   ##
150   # Accepts and stores the Module where the method is defined in as well as
151   # the CompiledMethod itself. Class of the object the method was extracted
152   # from can be given but will not be stored. This is always used internally
153   # only.
155   def initialize(mod, compiled_method, pulled_from = nil)
156     @defined_in       = mod
157     @compiled_method  = compiled_method
158     @pulled_from      = pulled_from
159   end
161   attr_reader :compiled_method
162   attr_reader :defined_in
164   ##
165   # UnboundMethod objects are equal if and only if they refer to the same
166   # method. One may be an alias for the other or both for a common one. Both
167   # must have been extracted from the same class or subclass. Two from
168   # different subclasses will not be considered equal.
170   def ==(other)
171     return true if other.kind_of? UnboundMethod and
172                    @defined_in == other.defined_in and
173                    @compiled_method == other.compiled_method
175     false
176   end
178   ##
179   # See Method#arity.
181   def arity()
182     @compiled_method.required
183   end
185   ##
186   # Creates a new Method object by attaching this method to the supplied
187   # receiver. The receiver must be kind_of? the Module that the method in
188   # question is defined in.
189   #
190   # Notably, this is a difference from MRI which requires that the object is
191   # of the exact Module the method was extracted from. This is safe because
192   # any overridden method will be identified as being defined in a different
193   # Module anyway.
195   def bind(receiver)
196     unless receiver.kind_of? @defined_in
197       raise TypeError, "Must be bound to an object of kind #{@defined_in}"
198     end
199     Method.new receiver, @defined_in, @compiled_method
200   end
202   ##
203   # Convenience method for #binding to the given receiver object and calling
204   # it with the optionally supplied arguments.
206   def call_on_instance(obj, *args)
207     bind(obj).call(*args)
208   end
210   ##
211   # String representation for UnboundMethod includes the method name, the
212   # Module it is defined in and the Module that it was extracted from.
214   def inspect()
215     "#<#{self.class}: #{@pulled_from}##{@compiled_method.name} (defined in #{@defined_in})>"
216   end
218   alias_method :to_s, :inspect