class Base def foo() :base end end class Derived < Base def foo() :derived end end class Interpreter attr_accessor :ret def do_a() @ret += "there, "; end def do_d() @ret += "Hello "; end def do_e() @ret += "!\n"; end def do_v() @ret += "Dave"; end Dispatcher = { "a" => instance_method(:do_a), "d" => instance_method(:do_d), "e" => instance_method(:do_e), "v" => instance_method(:do_v) } def interpret(string) @ret = "" string.split("").each {|b| Dispatcher[b].bind(self).call } end end assert 'demo' do interpreter = Interpreter.new interpreter.interpret('dave') assert_equal "Hello there, Dave!\n", interpreter.ret end assert 'Method#arity' do Class.new { attr_accessor :done def initialize; @done = false; end def m0() end def m1(a) end def m2(a, b) end def mo1(a = nil, &b) end def mo2(a, b = nil) end def mo3(*a) end def mo4(a, *b, &c) end def mo5(a, *b, c) end def mo6(a, *b, c, &d) end def mo7(a, b = nil, *c, d, &e) end def ma1((a), &b) nil && a end def run assert_equal(0, method(:m0).arity) assert_equal(1, method(:m1).arity) assert_equal(2, method(:m2).arity) assert_equal(-1, method(:mo1).arity) assert_equal(-2, method(:mo2).arity) assert_equal(-1, method(:mo3).arity) assert_equal(-2, method(:mo4).arity) assert_equal(-3, method(:mo5).arity) assert_equal(-3, method(:mo6).arity) assert_equal(-3, method(:mo7).arity) assert_equal(1, method(:ma1).arity) assert_equal(-2, method(:__send__).arity) assert_equal(-1, method(:nothing).arity) end def respond_to_missing?(m, b) m == :nothing end }.new.run end assert 'Method and UnboundMethod should not be have a `new` method' do assert_raise(NoMethodError){ Method.new } assert_raise(NoMethodError){ UnboundMethod.new } end assert 'instance' do assert_kind_of Method, 1.method(:+) assert_kind_of UnboundMethod, Integer.instance_method(:+) end assert 'Method#call' do assert_equal 3, 1.method(:+).call(2) assert_equal "ab", "a".method(:+)["b"] klass = Class.new { def foo; 42; end } klass2 = Class.new(klass) { def foo; super; end } assert_equal 42, klass2.new.method(:foo).call i = Class.new { def bar yield 3 end }.new assert_raise(LocalJumpError) { i.method(:bar).call } assert_equal 3, i.method(:bar).call { |i| i } assert_raise(ArgumentError) { nil.method(:__id__).call nil, 1 } assert_raise(ArgumentError) { nil.method(:__id__).call nil, opts: 1 } end assert 'Method#call for regression' do obj = BasicObject.new assert_equal String, Kernel.instance_method(:inspect).bind(obj).call().class, "https://github.com/ksss/mruby-method/issues/4" end assert 'Method#call with undefined method' do c = Class.new { attr_accessor :m, :argv def respond_to_missing?(m, b) m == :foo end def method_missing(m, *argv) @m = m @argv = argv super end } cc = c.new assert_raise(NameError) { cc.method(:nothing) } assert_kind_of Method, cc.method(:foo) assert_raise(NoMethodError) { cc.method(:foo).call(:arg1, :arg2) } assert_equal :foo, cc.m assert_equal [:arg1, :arg2], cc.argv cc = c.new m = cc.method(:foo) c.class_eval do def foo :ng end end assert_raise(NoMethodError) { m.call(:arg1, :arg2) } end assert 'Method#call with undefined method -- only kwargs' do c = Class.new { attr_accessor :m, :argv, :kwargs def respond_to_missing?(m, b) m == :foo end def method_missing(m, *argv, **kwargs) @m = m @argv = argv @kwargs = kwargs super end } cc = c.new assert_kind_of Method, cc.method(:foo) # Calling cc.method(:foo) works assert_raise(NoMethodError) { cc.method(:foo).call(kwarg1: :val1, kwarg2: :val2) } assert_equal :foo, cc.m assert_equal [], cc.argv assert_equal({ kwarg1: :val1, kwarg2: :val2 }, cc.kwargs) # calling cc.foo fails assert_raise(NoMethodError) { cc.foo(kwarg1: :val1, kwarg2: :val2) } assert_equal :foo, cc.m assert_equal [], cc.argv assert_equal({ kwarg1: :val1, kwarg2: :val2 }, cc.kwargs) end assert 'Method#source_location' do skip if proc{}.source_location.nil? filename = __FILE__ klass = Class.new lineno = __LINE__ + 1 klass.define_method(:find_me_if_you_can) {} assert_equal [filename, lineno], klass.new.method(:find_me_if_you_can).source_location lineno = __LINE__ + 1 class < { } values = { x: 1, y: 2, z: 3, w: [4, 5, 6], u: 7, v: 8, opts: { s: 9, t: 10 }, blk: blk } assert_equal values, o.method(:baz).to_proc.call(1, 2, 3, 4, 5, 6, u: 7, v: 8, s: 9, t: 10, &blk) assert_equal values, o.method(:baz).to_proc.call(1, 2, 3, 4, 5, 6, **{ u: 7, v: 8, s: 9, t: 10 }, &blk) assert_equal values, o.method(:baz).to_proc.call(*[1, 2, 3, 4, 5, 6], u: 7, v: 8, s: 9, t: 10, &blk) assert_equal values, o.method(:baz).to_proc.call(*[1, 2, 3, 4, 5, 6], **{ u: 7, v: 8, s: 9, t: 10 }, &blk) assert_raise(ArgumentError) { nil.method(:__id__).to_proc.call nil, 1 } assert_raise(ArgumentError) { nil.method(:__id__).to_proc.call nil, opts: 1 } end assert 'to_s' do o = Object.new def o.foo; end m = o.method(:foo) assert_match("#", m.unbind.inspect) c = Class.new c.class_eval { def foo; end; } m = c.new.method(:foo) assert_match("#", m.inspect) m = c.instance_method(:foo) assert_match("#", m.inspect) end assert 'owner' do c = Class.new do def foo; end def self.bar; end end m = Module.new do def baz; end end c.include(m) c2 = Class.new(c) assert_equal(c, c.instance_method(:foo).owner) assert_equal(c, c2.instance_method(:foo).owner) assert_equal(c, c.new.method(:foo).owner) assert_equal(c, c2.new.method(:foo).owner) assert_equal((class <>" do obj = Object.new class << obj def mul2(n); n * 2; end def add3(n); n + 3; end end f = obj.method(:mul2) g = obj.method(:add3) m1 = f << g assert_kind_of Proc, m1 assert_equal 16, m1.call(5) m2 = f >> g assert_kind_of Proc, m2 assert_equal 13, m2.call(5) end assert 'UnboundMethod#arity' do c = Class.new { def foo(a, b) end def respond_to_missing?(m, b) m == :nothing end } assert_equal 2, c.instance_method(:foo).arity assert_equal(-1, c.new.method(:nothing).unbind.arity) end assert 'UnboundMethod#==' do assert_false(Integer.instance_method(:+) == Integer.instance_method(:-)) assert_true(Integer.instance_method(:+) == Integer.instance_method(:+)) assert_true(UnboundMethod.instance_method(:==) == UnboundMethod.instance_method(:eql?)) skip unless Object.const_defined?(:Float) assert_false(Integer.instance_method(:+) == Float.instance_method(:+)) end assert 'UnboundMethod#super_method' do m = Derived.instance_method(:foo) m = m.super_method assert_equal(Base.instance_method(:foo), m) assert_nil(m.super_method) m = Object.instance_method(:object_id) assert_nil(m.super_method) end assert 'UnboundMethod#bind' do m = Module.new{ def meth() :meth end }.instance_method(:meth) assert_raise(ArgumentError) { m.bind } assert_kind_of Method, m.bind(1) assert_kind_of Method, m.bind(:sym) assert_kind_of Method, m.bind(Object.new) assert_equal(:meth, m.bind(1).call) assert_equal(:meth, m.bind(:sym).call) assert_equal(:meth, m.bind(Object.new).call) sc = nil Class.new { sc = class << self def foo end self end } assert_raise(TypeError) { sc.instance_method(:foo).bind([]) } assert_raise(TypeError) { Array.instance_method(:each).bind(1) } assert_kind_of Method, Object.instance_method(:object_id).bind(Object.new) end assert 'UnboundMethod#bind_call' do m = Array.instance_method(:size) assert_equal(:size, m.name) assert_equal(0, m.bind_call([])) assert_equal(1, m.bind_call([1])) assert_equal(2, m.bind_call([1,2])) o = Object.new def m(x, y, z, *w, u:, v:, **opts, &blk) { x:, y:, z:, w:, u:, v:, opts:, blk: } end m = o.method(:m).unbind blk = -> { } values = { x: 1, y: 2, z: 3, w: [4, 5, 6], u: 7, v: 8, opts: { s: 9, t: 10 }, blk: blk } assert_equal values, m.bind_call(o, 1, 2, 3, 4, 5, 6, u: 7, v: 8, s: 9, t: 10, &blk) assert_equal values, m.bind_call(o, 1, 2, 3, 4, 5, 6, **{ u: 7, v: 8, s: 9, t: 10 }, &blk) assert_equal values, m.bind_call(o, *[1, 2, 3, 4, 5, 6], u: 7, v: 8, s: 9, t: 10, &blk) assert_equal values, m.bind_call(o, *[1, 2, 3, 4, 5, 6], **{ u: 7, v: 8, s: 9, t: 10 }, &blk) assert_raise(ArgumentError) { m.bind_call } assert_raise(ArgumentError) { BasicObject.instance_method(:__id__).bind_call nil, 1 } assert_raise(ArgumentError) { BasicObject.instance_method(:__id__).bind_call nil, opts: 1 } end assert 'Method#parameters and #arity on aliased methods' do # Regression: an alias proc carries the original method's name in body.mid # (not an irep), with `upper` pointing at the original proc. Both # mrb_proc_parameters (mruby-proc-ext) and mrb_proc_arity (core src/proc.c) # used to fall into their irep branch for alias procs and dereference body.mid # as an mrb_irep* -> SEGV / misaligned read. Both must resolve through `upper`. # # The literals below match CRuby for these positional/optional/rest/block # signatures (where mruby and CRuby agree), so this is independent ground # truth, not merely "alias == original". c = Class.new { def f0; end def f1(a); end def fopt(a, b = 1); end def frest(a, *b); end def fblk(a, &b); end def fmix(a, b = 1, *c, &d); end alias_method :a0, :f0 alias_method :a1, :f1 alias_method :aopt, :fopt alias_method :arest, :frest alias_method :ablk, :fblk alias_method :amix, :fmix } cases = [ # name, parameters, arity [:a0, [], 0], [:a1, [[:req, :a]], 1], [:aopt, [[:req, :a], [:opt, :b]], -2], [:arest, [[:req, :a], [:rest, :b]], -2], [:ablk, [[:req, :a], [:block, :b]], 1], [:amix, [[:req, :a], [:opt, :b], [:rest, :c], [:block, :d]], -2], ] cases.each do |name, params, arity| u = c.instance_method(name) assert_equal params, u.parameters assert_equal arity, u.arity # the bound Method goes through the same proc paths and must agree b = c.new.method(name) assert_equal params, b.parameters assert_equal arity, b.arity end # alias must equal the original it points at (parameters and arity) { a0: :f0, a1: :f1, aopt: :fopt, arest: :frest, ablk: :fblk, amix: :fmix }.each do |al, orig| assert_equal c.instance_method(orig).parameters, c.instance_method(al).parameters assert_equal c.instance_method(orig).arity, c.instance_method(al).arity end # Alias-of-an-alias collapses to one proc at creation; must still resolve. chain = Class.new { def orig(x, y) end alias_method :a1, :orig alias_method :a2, :a1 } assert_equal [[:req, :x], [:req, :y]], chain.instance_method(:a2).parameters assert_equal 2, chain.instance_method(:a2).arity # Aliasing a C method does NOT create an alias proc (it reuses the original # cfunc method), so it must behave exactly like the original and never crash. c2 = Class.new(String) { alias_method :up2, :upcase } assert_equal String.instance_method(:upcase).parameters, c2.instance_method(:up2).parameters assert_equal String.instance_method(:upcase).arity, c2.instance_method(:up2).arity end assert 'Method/UnboundMethod on C-defined (native) methods' do # C methods have no irep: arity/parameters come from the packed argument spec # (caspec) and source_location is always nil. This exercises the # MRB_PROC_CFUNC_P branches of mrb_proc_arity / mrb_proc_parameters and the # cfunc path of method_search_vm -- none of which the suite covered before. # source_location is nil for genuinely C-defined methods (not mrblib ones). assert_nil "x".method(:upcase).source_location assert_nil 1.method(:+).source_location assert_nil [].method(:push).source_location assert_nil String.instance_method(:upcase).source_location # arity: documented values for these core C methods. assert_equal 0, "x".method(:upcase).arity # no args assert_equal 1, 1.method(:+).arity # one required assert_equal(-1, [].method(:push).arity) # variadic (rest) assert_equal(-1, [].method(:first).arity) # optional # parameters: always an Array of Arrays, never crashes; a no-arg C method # gives []. C-method parameter *kinds* are an approximation (names are absent, # and required args surface as :opt for non-strict procs), so we assert the # stable shape rather than pinning exact kind labels -- except :rest, which is # meaningful: a variadic C method must expose a rest parameter. assert_equal [], "x".method(:upcase).parameters [1.method(:+), [].method(:push), {}.method(:[]), [].method(:first)].each do |m| ps = m.parameters assert_true ps.is_a?(Array) ps.each { |p| assert_true p.is_a?(Array) } end assert_true [].method(:push).parameters.any? { |entry| entry[0] == :rest } # identity / metadata on a C method. m = "abc".method(:upcase) assert_equal String, m.owner assert_equal :upcase, m.name assert_equal "abc", m.receiver assert_equal "#", m.to_s assert_equal "#", String.instance_method(:upcase).to_s # behaviour: the C function actually runs via call / [] / bind / bind_call / # unbind+rebind. assert_equal 5, 2.method(:+).call(3) assert_equal 5, 2.method(:+)[3] assert_equal 5, Integer.instance_method(:+).bind_call(2, 3) assert_equal 5, Integer.instance_method(:+).bind(2).call(3) assert_equal 11, 5.method(:+).unbind.bind(10).call(1) # eql?: equal only when bound to the SAME receiver object and same definition. s = "cat" assert_true s.method(:upcase) == s.method(:upcase) assert_false s.method(:upcase) == "cat".method(:upcase) # distinct receivers assert_false s.method(:upcase) == s.method(:downcase) # super_method resolves across C methods (Integer#to_s -> BasicObject#to_s). sm = 5.method(:to_s).super_method assert_false sm.nil? assert_equal :to_s, sm.name # binding a C UnboundMethod to an incompatible receiver still raises cleanly. assert_raise(TypeError) { String.instance_method(:upcase).bind(42) } end