core/method.rb
##
# 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
private :initialize
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, callable_proc = nil)
Rubinius::Type.bindable_method? self.defined_in, klass
scope = @executable.scope
if @executable.is_a? Rubinius::DelegatedMethod
code = @executable
else
if callable_proc
code = Rubinius::DelegatedMethod.new(name, :call, callable_proc, false)
else
code = Rubinius::DelegatedMethod.new(name, :call_on_instance, self.unbind, true)
end
end
[code, scope]
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
private :initialize
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, callable_proc = nil)
Rubinius::Type.bindable_method? self.defined_in, klass
case @executable
when Rubinius::AccessVariable
code = @executable
scope = nil
when Rubinius::DelegatedMethod
code = @executable
scope = code.scope
else
if callable_proc
code = Rubinius::DelegatedMethod.new(name, :call, callable_proc, false)
else
code = Rubinius::DelegatedMethod.new(name, :call_on_instance, self, true)
end
scope = @executable.scope
end
[code, scope]
end
end