ruby/test/ruby/test_method.rb
Aaron Patterson cdf33ed5f3 Optimized forwarding callers and callees
This patch optimizes forwarding callers and callees. It only optimizes methods that only take `...` as their parameter, and then pass `...` to other calls.

Calls it optimizes look like this:

```ruby
def bar(a) = a
def foo(...) = bar(...) # optimized
foo(123)
```

```ruby
def bar(a) = a
def foo(...) = bar(1, 2, ...) # optimized
foo(123)
```

```ruby
def bar(*a) = a

def foo(...)
  list = [1, 2]
  bar(*list, ...) # optimized
end
foo(123)
```

All variants of the above but using `super` are also optimized, including a bare super like this:

```ruby
def foo(...)
  super
end
```

This patch eliminates intermediate allocations made when calling methods that accept `...`.
We can observe allocation elimination like this:

```ruby
def m
  x = GC.stat(:total_allocated_objects)
  yield
  GC.stat(:total_allocated_objects) - x
end

def bar(a) = a
def foo(...) = bar(...)

def test
  m { foo(123) }
end

test
p test # allocates 1 object on master, but 0 objects with this patch
```

```ruby
def bar(a, b:) = a + b
def foo(...) = bar(...)

def test
  m { foo(1, b: 2) }
end

test
p test # allocates 2 objects on master, but 0 objects with this patch
```

How does it work?
-----------------

This patch works by using a dynamic stack size when passing forwarded parameters to callees.
The caller's info object (known as the "CI") contains the stack size of the
parameters, so we pass the CI object itself as a parameter to the callee.
When forwarding parameters, the forwarding ISeq uses the caller's CI to determine how much stack to copy, then copies the caller's stack before calling the callee.
The CI at the forwarded call site is adjusted using information from the caller's CI.

I think this description is kind of confusing, so let's walk through an example with code.

```ruby
def delegatee(a, b) = a + b

def delegator(...)
  delegatee(...)  # CI2 (FORWARDING)
end

def caller
  delegator(1, 2) # CI1 (argc: 2)
end
```

Before we call the delegator method, the stack looks like this:

```
Executing Line | Code                                  | Stack
---------------+---------------------------------------+--------
              1| def delegatee(a, b) = a + b           | self
              2|                                       | 1
              3| def delegator(...)                    | 2
              4|   #                                   |
              5|   delegatee(...)  # CI2 (FORWARDING)  |
              6| end                                   |
              7|                                       |
              8| def caller                            |
          ->  9|   delegator(1, 2) # CI1 (argc: 2)     |
             10| end                                   |
```

The ISeq for `delegator` is tagged as "forwardable", so when `caller` calls in
to `delegator`, it writes `CI1` on to the stack as a local variable for the
`delegator` method.  The `delegator` method has a special local called `...`
that holds the caller's CI object.

Here is the ISeq disasm fo `delegator`:

```
== disasm: #<ISeq:delegator@-e:1 (1,0)-(1,39)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] "..."@0
0000 putself                                                          (   1)[LiCa]
0001 getlocal_WC_0                          "..."@0
0003 send                                   <calldata!mid:delegatee, argc:0, FCALL|FORWARDING>, nil
0006 leave                                  [Re]
```

The local called `...` will contain the caller's CI: CI1.

Here is the stack when we enter `delegator`:

```
Executing Line | Code                                  | Stack
---------------+---------------------------------------+--------
              1| def delegatee(a, b) = a + b           | self
              2|                                       | 1
              3| def delegator(...)                    | 2
           -> 4|   #                                   | CI1 (argc: 2)
              5|   delegatee(...)  # CI2 (FORWARDING)  | cref_or_me
              6| end                                   | specval
              7|                                       | type
              8| def caller                            |
              9|   delegator(1, 2) # CI1 (argc: 2)     |
             10| end                                   |
```

The CI at `delegatee` on line 5 is tagged as "FORWARDING", so it knows to
memcopy the caller's stack before calling `delegatee`.  In this case, it will
memcopy self, 1, and 2 to the stack before calling `delegatee`.  It knows how much
memory to copy from the caller because `CI1` contains stack size information
(argc: 2).

Before executing the `send` instruction, we push `...` on the stack.  The
`send` instruction pops `...`, and because it is tagged with `FORWARDING`, it
knows to memcopy (using the information in the CI it just popped):

```
== disasm: #<ISeq:delegator@-e:1 (1,0)-(1,39)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] "..."@0
0000 putself                                                          (   1)[LiCa]
0001 getlocal_WC_0                          "..."@0
0003 send                                   <calldata!mid:delegatee, argc:0, FCALL|FORWARDING>, nil
0006 leave                                  [Re]
```

Instruction 001 puts the caller's CI on the stack.  `send` is tagged with
FORWARDING, so it reads the CI and _copies_ the callers stack to this stack:

```
Executing Line | Code                                  | Stack
---------------+---------------------------------------+--------
              1| def delegatee(a, b) = a + b           | self
              2|                                       | 1
              3| def delegator(...)                    | 2
              4|   #                                   | CI1 (argc: 2)
           -> 5|   delegatee(...)  # CI2 (FORWARDING)  | cref_or_me
              6| end                                   | specval
              7|                                       | type
              8| def caller                            | self
              9|   delegator(1, 2) # CI1 (argc: 2)     | 1
             10| end                                   | 2
```

The "FORWARDING" call site combines information from CI1 with CI2 in order
to support passing other values in addition to the `...` value, as well as
perfectly forward splat args, kwargs, etc.

Since we're able to copy the stack from `caller` in to `delegator`'s stack, we
can avoid allocating objects.

I want to do this to eliminate object allocations for delegate methods.
My long term goal is to implement `Class#new` in Ruby and it uses `...`.

I was able to implement `Class#new` in Ruby
[here](https://github.com/ruby/ruby/pull/9289).
If we adopt the technique in this patch, then we can optimize allocating
objects that take keyword parameters for `initialize`.

For example, this code will allocate 2 objects: one for `SomeObject`, and one
for the kwargs:

```ruby
SomeObject.new(foo: 1)
```

If we combine this technique, plus implement `Class#new` in Ruby, then we can
reduce allocations for this common operation.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
Co-Authored-By: Alan Wu <XrXr@users.noreply.github.com>
2024-06-18 09:28:25 -07:00

1724 lines
53 KiB
Ruby

# -*- coding: us-ascii -*-
# frozen_string_literal: false
require 'test/unit'
class TestMethod < Test::Unit::TestCase
def setup
@verbose = $VERBOSE
end
def teardown
$VERBOSE = @verbose
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 mo8(a, b = nil, *, d, &e) end
def ma1((a), &b) nil && a end
def mk1(**) end
def mk2(**o) nil && o end
def mk3(a, **o) nil && o end
def mk4(a = nil, **o) nil && o end
def mk5(a, b = nil, **o) nil && o end
def mk6(a, b = nil, c, **o) nil && o end
def mk7(a, b = nil, *c, d, **o) nil && o end
def mk8(a, b = nil, *c, d, e:, f: nil, **o) nil && o end
def mnk(**nil) end
def mf(...) end
class Base
def foo() :base end
end
class Derived < Base
def foo() :derived end
end
class T
def initialize; end
def initialize_copy(*) super end
def initialize_clone(*) super end
def initialize_dup(*) super end
def respond_to_missing?(*) super end
def normal_method; end
end
module M
def func; end
module_function :func
def meth; :meth end
end
def mv1() end
def mv2() end
private :mv2
def mv3() end
protected :mv3
class Visibility
def mv1() end
def mv2() end
private :mv2
def mv3() end
protected :mv3
end
def test_arity
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(-1, method(:mk1).arity)
assert_equal(-1, method(:mk2).arity)
assert_equal(-2, method(:mk3).arity)
assert_equal(-1, method(:mk4).arity)
assert_equal(-2, method(:mk5).arity)
assert_equal(-3, method(:mk6).arity)
assert_equal(-3, method(:mk7).arity)
end
def test_arity_special
assert_equal(-1, method(:__send__).arity)
end
def test_unbind
assert_equal(:derived, Derived.new.foo)
um = Derived.new.method(:foo).unbind
assert_instance_of(UnboundMethod, um)
Derived.class_eval do
def foo() :changed end
end
assert_equal(:changed, Derived.new.foo)
assert_equal(:derived, um.bind(Derived.new).call)
assert_raise(TypeError) do
um.bind(Base.new)
end
# cleanup
Derived.class_eval do
remove_method :foo
def foo() :derived; end
end
end
def test_callee
assert_equal(:test_callee, __method__)
assert_equal(:m, Class.new {def m; __method__; end}.new.m)
assert_equal(:m, Class.new {def m; tap{return __method__}; end}.new.m)
assert_equal(:m, Class.new {define_method(:m) {__method__}}.new.m)
assert_equal(:m, Class.new {define_method(:m) {tap{return __method__}}}.new.m)
assert_nil(eval("class TestCallee; __method__; end"))
assert_equal(:test_callee, __callee__)
[
["method", Class.new {def m; __callee__; end},],
["block", Class.new {def m; tap{return __callee__}; end},],
["define_method", Class.new {define_method(:m) {__callee__}}],
["define_method block", Class.new {define_method(:m) {tap{return __callee__}}}],
].each do |mesg, c|
c.class_eval {alias m2 m}
o = c.new
assert_equal(:m, o.m, mesg)
assert_equal(:m2, o.m2, mesg)
end
assert_nil(eval("class TestCallee; __callee__; end"))
end
def test_orphan_callee
c = Class.new{def foo; proc{__callee__}; end; alias alias_foo foo}
assert_equal(:alias_foo, c.new.alias_foo.call, '[Bug #11046]')
end
def test_method_in_define_method_block
bug4606 = '[ruby-core:35386]'
c = Class.new do
[:m1, :m2].each do |m|
define_method(m) do
__method__
end
end
end
assert_equal(:m1, c.new.m1, bug4606)
assert_equal(:m2, c.new.m2, bug4606)
end
def test_method_in_block_in_define_method_block
bug4606 = '[ruby-core:35386]'
c = Class.new do
[:m1, :m2].each do |m|
define_method(m) do
tap { return __method__ }
end
end
end
assert_equal(:m1, c.new.m1, bug4606)
assert_equal(:m2, c.new.m2, bug4606)
end
def test_body
o = Object.new
def o.foo; end
assert_nothing_raised { RubyVM::InstructionSequence.disasm(o.method(:foo)) }
assert_nothing_raised { RubyVM::InstructionSequence.disasm("x".method(:upcase)) }
assert_nothing_raised { RubyVM::InstructionSequence.disasm(method(:to_s).to_proc) }
end
def test_new
c1 = Class.new
c1.class_eval { def foo; :foo; end }
c2 = Class.new(c1)
c2.class_eval { private :foo }
o = c2.new
o.extend(Module.new)
assert_raise(NameError) { o.method(:bar) }
assert_raise(NameError) { o.public_method(:foo) }
assert_equal(:foo, o.method(:foo).call)
end
def test_eq
o = Object.new
class << o
def foo; end
alias bar foo
def baz; end
end
assert_not_equal(o.method(:foo), nil)
m = o.method(:foo)
def m.foo; end
assert_not_equal(o.method(:foo), m)
assert_equal(o.method(:foo), o.method(:foo))
assert_equal(o.method(:foo), o.method(:bar))
assert_not_equal(o.method(:foo), o.method(:baz))
end
def test_hash
o = Object.new
def o.foo; end
assert_kind_of(Integer, o.method(:foo).hash)
assert_equal(Array.instance_method(:map).hash, Array.instance_method(:collect).hash)
assert_kind_of(String, o.method(:foo).hash.to_s)
end
def test_owner
c = Class.new do
def foo; end
end
assert_equal(c, c.instance_method(:foo).owner)
c2 = Class.new(c)
assert_equal(c, c2.instance_method(:foo).owner)
end
def test_owner_missing
c = Class.new do
def respond_to_missing?(name, bool)
name == :foo
end
end
c2 = Class.new(c)
assert_equal(c, c.new.method(:foo).owner)
assert_equal(c2, c2.new.method(:foo).owner)
end
def test_receiver_name_owner
o = Object.new
def o.foo; end
m = o.method(:foo)
assert_equal(o, m.receiver)
assert_equal(:foo, m.name)
assert_equal(class << o; self; end, m.owner)
assert_equal(:foo, m.unbind.name)
assert_equal(class << o; self; end, m.unbind.owner)
class << o
alias bar foo
end
m = o.method(:bar)
assert_equal(:bar, m.name)
assert_equal(:foo, m.original_name)
end
def test_instance_method
c = Class.new
c.class_eval do
def foo; :foo; end
private :foo
end
o = c.new
o.method(:foo).unbind
assert_raise(NoMethodError) { o.foo }
c.instance_method(:foo).bind(o)
assert_equal(:foo, o.instance_eval { foo })
assert_raise(NameError) { c.public_instance_method(:foo) }
def o.bar; end
m = o.method(:bar).unbind
assert_raise(TypeError) { m.bind(Object.new) }
cx = EnvUtil.labeled_class("X\u{1f431}")
assert_raise_with_message(TypeError, /X\u{1f431}/) do
o.method(cx)
end
end
def test_bind_module_instance_method
feature4254 = '[ruby-core:34267]'
m = M.instance_method(:meth)
assert_equal(:meth, m.bind(Object.new).call, feature4254)
end
def test_define_method
c = Class.new
c.class_eval { def foo; :foo; end }
o = c.new
def o.bar; :bar; end
assert_raise(TypeError) do
c.class_eval { define_method(:foo, :foo) }
end
assert_raise(ArgumentError) do
c.class_eval { define_method }
end
c2 = Class.new(c)
c2.class_eval { define_method(:baz, o.method(:foo)) }
assert_equal(:foo, c2.new.baz)
assert_raise(TypeError) do
Class.new.class_eval { define_method(:foo, o.method(:foo)) }
end
assert_raise(TypeError) do
Class.new.class_eval { define_method(:bar, o.method(:bar)) }
end
cx = EnvUtil.labeled_class("X\u{1f431}")
assert_raise_with_message(TypeError, /X\u{1F431}/) do
Class.new {define_method(cx) {}}
end
end
def test_define_method_no_proc
o = Object.new
def o.foo(c)
c.class_eval { define_method(:foo) }
end
c = Class.new
assert_raise(ArgumentError) {o.foo(c)}
bug11283 = '[ruby-core:69655] [Bug #11283]'
assert_raise(ArgumentError, bug11283) {o.foo(c) {:foo}}
end
def test_define_singleton_method
o = Object.new
o.instance_eval { define_singleton_method(:foo) { :foo } }
assert_equal(:foo, o.foo)
end
PUBLIC_SINGLETON_TEST = Object.new
class << PUBLIC_SINGLETON_TEST
private
PUBLIC_SINGLETON_TEST.define_singleton_method(:dsm){}
def PUBLIC_SINGLETON_TEST.def; end
end
def test_define_singleton_method_public
assert_nil(PUBLIC_SINGLETON_TEST.dsm)
assert_nil(PUBLIC_SINGLETON_TEST.def)
end
def test_define_singleton_method_no_proc
o = Object.new
assert_raise(ArgumentError) {
o.instance_eval { define_singleton_method(:bar) }
}
bug11283 = '[ruby-core:69655] [Bug #11283]'
def o.define(n)
define_singleton_method(n)
end
assert_raise(ArgumentError, bug11283) {o.define(:bar) {:bar}}
end
def test_define_method_invalid_arg
assert_raise(TypeError) do
Class.new.class_eval { define_method(:foo, Object.new) }
end
assert_raise(TypeError) do
Module.new.module_eval {define_method(:foo, Base.instance_method(:foo))}
end
end
def test_define_singleton_method_with_extended_method
bug8686 = "[ruby-core:56174]"
m = Module.new do
extend self
def a
"a"
end
end
assert_nothing_raised(bug8686) do
m.define_singleton_method(:a, m.method(:a))
end
end
def test_define_method_transplating
feature4254 = '[ruby-core:34267]'
m = Module.new {define_method(:meth, M.instance_method(:meth))}
assert_equal(:meth, Object.new.extend(m).meth, feature4254)
c = Class.new {define_method(:meth, M.instance_method(:meth))}
assert_equal(:meth, c.new.meth, feature4254)
end
def test_define_method_visibility
c = Class.new do
public
define_method(:foo) {:foo}
protected
define_method(:bar) {:bar}
private
define_method(:baz) {:baz}
end
assert_equal(true, c.public_method_defined?(:foo))
assert_equal(false, c.public_method_defined?(:bar))
assert_equal(false, c.public_method_defined?(:baz))
assert_equal(false, c.protected_method_defined?(:foo))
assert_equal(true, c.protected_method_defined?(:bar))
assert_equal(false, c.protected_method_defined?(:baz))
assert_equal(false, c.private_method_defined?(:foo))
assert_equal(false, c.private_method_defined?(:bar))
assert_equal(true, c.private_method_defined?(:baz))
m = Module.new do
module_function
define_method(:foo) {:foo}
end
assert_equal(true, m.respond_to?(:foo))
assert_equal(false, m.public_method_defined?(:foo))
assert_equal(false, m.protected_method_defined?(:foo))
assert_equal(true, m.private_method_defined?(:foo))
end
def test_define_method_in_private_scope
bug9005 = '[ruby-core:57747] [Bug #9005]'
c = Class.new
class << c
public :define_method
end
TOPLEVEL_BINDING.eval("proc{|c|c.define_method(:x) {|x|throw x}}").call(c)
o = c.new
assert_throw(bug9005) {o.x(bug9005)}
end
def test_singleton_define_method_in_private_scope
bug9141 = '[ruby-core:58497] [Bug #9141]'
o = Object.new
class << o
public :define_singleton_method
end
TOPLEVEL_BINDING.eval("proc{|o|o.define_singleton_method(:x) {|x|throw x}}").call(o)
assert_throw(bug9141) do
o.x(bug9141)
end
end
def test_super_in_proc_from_define_method
c1 = Class.new {
def m
:m1
end
}
c2 = Class.new(c1) { define_method(:m) { Proc.new { super() } } }
assert_equal(:m1, c2.new.m.call, 'see [Bug #4881] and [Bug #3136]')
end
def test_clone
o = Object.new
def o.foo; :foo; end
m = o.method(:foo)
def m.bar; :bar; end
assert_equal(:foo, m.clone.call)
assert_equal(:bar, m.clone.bar)
end
def test_clone_under_gc_compact_stress
omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
EnvUtil.under_gc_compact_stress do
o = Object.new
def o.foo; :foo; end
m = o.method(:foo)
def m.bar; :bar; end
assert_equal(:foo, m.clone.call)
assert_equal(:bar, m.clone.bar)
end
end
def test_inspect
o = Object.new
def o.foo; end; line_no = __LINE__
m = o.method(:foo)
assert_equal("#<Method: #{ o.inspect }.foo() #{__FILE__}:#{line_no}>", m.inspect)
m = o.method(:foo)
assert_match("#<UnboundMethod: #{ class << o; self; end.inspect }#foo() #{__FILE__}:#{line_no}", m.unbind.inspect)
c = Class.new
c.class_eval { def foo; end; }; line_no = __LINE__
m = c.new.method(:foo)
assert_equal("#<Method: #{ c.inspect }#foo() #{__FILE__}:#{line_no}>", m.inspect)
m = c.instance_method(:foo)
assert_equal("#<UnboundMethod: #{ c.inspect }#foo() #{__FILE__}:#{line_no}>", m.inspect)
c2 = Class.new(c)
c2.class_eval { private :foo }
m2 = c2.new.method(:foo)
assert_equal("#<Method: #{ c2.inspect }(#{ c.inspect })#foo() #{__FILE__}:#{line_no}>", m2.inspect)
bug7806 = '[ruby-core:52048] [Bug #7806]'
c3 = Class.new(c)
c3.class_eval { alias bar foo }
m3 = c3.new.method(:bar)
assert_equal("#<Method: #{c3.inspect}(#{c.inspect})#bar(foo)() #{__FILE__}:#{line_no}>", m3.inspect, bug7806)
bug15608 = '[ruby-core:91570] [Bug #15608]'
c4 = Class.new(c)
c4.class_eval { alias bar foo }
o = c4.new
o.singleton_class
m4 = o.method(:bar)
assert_equal("#<Method: #{c4.inspect}(#{c.inspect})#bar(foo)() #{__FILE__}:#{line_no}>", m4.inspect, bug15608)
bug17428 = '[ruby-core:101635] [Bug #17428]'
assert_equal("#<Method: #<Class:String>(Module)#prepend(*)>", String.method(:prepend).inspect, bug17428)
c5 = Class.new(String)
m = Module.new{def prepend; end; alias prep prepend}; line_no = __LINE__
c5.extend(m)
c6 = Class.new(c5)
assert_equal("#<Method: #<Class:#{c6.inspect}>(#{m.inspect})#prep(prepend)() #{__FILE__}:#{line_no}>", c6.method(:prep).inspect, bug17428)
end
def test_callee_top_level
assert_in_out_err([], "p __callee__", %w(nil), [])
end
def test_caller_top_level
assert_in_out_err([], "p caller", %w([]), [])
end
def test_caller_negative_level
assert_raise(ArgumentError) { caller(-1) }
end
def test_attrset_ivar
c = Class.new
c.class_eval { attr_accessor :foo }
o = c.new
o.method(:foo=).call(42)
assert_equal(42, o.foo)
assert_raise(ArgumentError) { o.method(:foo=).call(1, 2, 3) }
assert_raise(ArgumentError) { o.method(:foo).call(1) }
end
def test_default_accessibility
tmethods = T.public_instance_methods
assert_include tmethods, :normal_method, 'normal methods are public by default'
assert_not_include tmethods, :initialize, '#initialize is private'
assert_not_include tmethods, :initialize_copy, '#initialize_copy is private'
assert_not_include tmethods, :initialize_clone, '#initialize_clone is private'
assert_not_include tmethods, :initialize_dup, '#initialize_dup is private'
assert_not_include tmethods, :respond_to_missing?, '#respond_to_missing? is private'
mmethods = M.public_instance_methods
assert_not_include mmethods, :func, 'module methods are private by default'
assert_include mmethods, :meth, 'normal methods are public by default'
end
def test_respond_to_missing_argument
obj = Struct.new(:mid).new
def obj.respond_to_missing?(id, *)
self.mid = id
true
end
assert_kind_of(Method, obj.method("bug15640"))
assert_kind_of(Symbol, obj.mid)
assert_equal("bug15640", obj.mid.to_s)
arg = Struct.new(:to_str).new("bug15640_2")
assert_kind_of(Method, obj.method(arg))
assert_kind_of(Symbol, obj.mid)
assert_equal("bug15640_2", obj.mid.to_s)
end
define_method(:pm0) {||}
define_method(:pm1) {|a|}
define_method(:pm2) {|a, b|}
define_method(:pmo1) {|a = nil, &b|}
define_method(:pmo2) {|a, b = nil|}
define_method(:pmo3) {|*a|}
define_method(:pmo4) {|a, *b, &c|}
define_method(:pmo5) {|a, *b, c|}
define_method(:pmo6) {|a, *b, c, &d|}
define_method(:pmo7) {|a, b = nil, *c, d, &e|}
define_method(:pma1) {|(a), &b| nil && a}
define_method(:pmk1) {|**|}
define_method(:pmk2) {|**o|}
define_method(:pmk3) {|a, **o|}
define_method(:pmk4) {|a = nil, **o|}
define_method(:pmk5) {|a, b = nil, **o|}
define_method(:pmk6) {|a, b = nil, c, **o|}
define_method(:pmk7) {|a, b = nil, *c, d, **o|}
define_method(:pmk8) {|a, b = nil, *c, d, e:, f: nil, **o|}
define_method(:pmnk) {|**nil|}
def test_bound_parameters
assert_equal([], method(:m0).parameters)
assert_equal([[:req, :a]], method(:m1).parameters)
assert_equal([[:req, :a], [:req, :b]], method(:m2).parameters)
assert_equal([[:opt, :a], [:block, :b]], method(:mo1).parameters)
assert_equal([[:req, :a], [:opt, :b]], method(:mo2).parameters)
assert_equal([[:rest, :a]], method(:mo3).parameters)
assert_equal([[:req, :a], [:rest, :b], [:block, :c]], method(:mo4).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c]], method(:mo5).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:mo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:mo7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], method(:mo8).parameters)
assert_equal([[:req], [:block, :b]], method(:ma1).parameters)
assert_equal([[:keyrest, :**]], method(:mk1).parameters)
assert_equal([[:keyrest, :o]], method(:mk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], method(:mk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], method(:mk4).parameters)
assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:mk5).parameters)
assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:mk6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:mk7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:mk8).parameters)
assert_equal([[:nokey]], method(:mnk).parameters)
# pending
assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], method(:mf).parameters)
end
def test_unbound_parameters
assert_equal([], self.class.instance_method(:m0).parameters)
assert_equal([[:req, :a]], self.class.instance_method(:m1).parameters)
assert_equal([[:req, :a], [:req, :b]], self.class.instance_method(:m2).parameters)
assert_equal([[:opt, :a], [:block, :b]], self.class.instance_method(:mo1).parameters)
assert_equal([[:req, :a], [:opt, :b]], self.class.instance_method(:mo2).parameters)
assert_equal([[:rest, :a]], self.class.instance_method(:mo3).parameters)
assert_equal([[:req, :a], [:rest, :b], [:block, :c]], self.class.instance_method(:mo4).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c]], self.class.instance_method(:mo5).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], self.class.instance_method(:mo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:mo7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], self.class.instance_method(:mo8).parameters)
assert_equal([[:req], [:block, :b]], self.class.instance_method(:ma1).parameters)
assert_equal([[:keyrest, :**]], self.class.instance_method(:mk1).parameters)
assert_equal([[:keyrest, :o]], self.class.instance_method(:mk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:mk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:mk4).parameters)
assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], self.class.instance_method(:mk5).parameters)
assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], self.class.instance_method(:mk6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:mk7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:mk8).parameters)
assert_equal([[:nokey]], self.class.instance_method(:mnk).parameters)
# pending
assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], self.class.instance_method(:mf).parameters)
end
def test_bmethod_bound_parameters
assert_equal([], method(:pm0).parameters)
assert_equal([[:req, :a]], method(:pm1).parameters)
assert_equal([[:req, :a], [:req, :b]], method(:pm2).parameters)
assert_equal([[:opt, :a], [:block, :b]], method(:pmo1).parameters)
assert_equal([[:req, :a], [:opt, :b]], method(:pmo2).parameters)
assert_equal([[:rest, :a]], method(:pmo3).parameters)
assert_equal([[:req, :a], [:rest, :b], [:block, :c]], method(:pmo4).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c]], method(:pmo5).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).parameters)
assert_equal([[:req], [:block, :b]], method(:pma1).parameters)
assert_equal([[:keyrest, :**]], method(:pmk1).parameters)
assert_equal([[:keyrest, :o]], method(:pmk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).parameters)
assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:pmk5).parameters)
assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:pmk6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:pmk7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:pmk8).parameters)
assert_equal([[:nokey]], method(:pmnk).parameters)
end
def test_bmethod_unbound_parameters
assert_equal([], self.class.instance_method(:pm0).parameters)
assert_equal([[:req, :a]], self.class.instance_method(:pm1).parameters)
assert_equal([[:req, :a], [:req, :b]], self.class.instance_method(:pm2).parameters)
assert_equal([[:opt, :a], [:block, :b]], self.class.instance_method(:pmo1).parameters)
assert_equal([[:req, :a], [:opt, :b]], self.class.instance_method(:pmo2).parameters)
assert_equal([[:rest, :a]], self.class.instance_method(:pmo3).parameters)
assert_equal([[:req, :a], [:rest, :b], [:block, :c]], self.class.instance_method(:pmo4).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c]], self.class.instance_method(:pmo5).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], self.class.instance_method(:pmo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:pmo7).parameters)
assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters)
assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters)
assert_equal([[:keyrest, :**]], self.class.instance_method(:pmk1).parameters)
assert_equal([[:keyrest, :o]], self.class.instance_method(:pmk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:pmk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:pmk4).parameters)
assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], self.class.instance_method(:pmk5).parameters)
assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], self.class.instance_method(:pmk6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:pmk7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:pmk8).parameters)
assert_equal([[:nokey]], self.class.instance_method(:pmnk).parameters)
end
def test_hidden_parameters
instance_eval("def m((_)"+",(_)"*256+");end")
assert_empty(method(:m).parameters.map{|_,n|n}.compact)
end
def test_method_parameters_inspect
assert_include(method(:m0).inspect, "()")
assert_include(method(:m1).inspect, "(a)")
assert_include(method(:m2).inspect, "(a, b)")
assert_include(method(:mo1).inspect, "(a=..., &b)")
assert_include(method(:mo2).inspect, "(a, b=...)")
assert_include(method(:mo3).inspect, "(*a)")
assert_include(method(:mo4).inspect, "(a, *b, &c)")
assert_include(method(:mo5).inspect, "(a, *b, c)")
assert_include(method(:mo6).inspect, "(a, *b, c, &d)")
assert_include(method(:mo7).inspect, "(a, b=..., *c, d, &e)")
assert_include(method(:mo8).inspect, "(a, b=..., *, d, &e)")
assert_include(method(:ma1).inspect, "(_, &b)")
assert_include(method(:mk1).inspect, "(**)")
assert_include(method(:mk2).inspect, "(**o)")
assert_include(method(:mk3).inspect, "(a, **o)")
assert_include(method(:mk4).inspect, "(a=..., **o)")
assert_include(method(:mk5).inspect, "(a, b=..., **o)")
assert_include(method(:mk6).inspect, "(a, b=..., c, **o)")
assert_include(method(:mk7).inspect, "(a, b=..., *c, d, **o)")
assert_include(method(:mk8).inspect, "(a, b=..., *c, d, e:, f: ..., **o)")
assert_include(method(:mnk).inspect, "(**nil)")
assert_include(method(:mf).inspect, "(...)")
end
def test_unbound_method_parameters_inspect
assert_include(self.class.instance_method(:m0).inspect, "()")
assert_include(self.class.instance_method(:m1).inspect, "(a)")
assert_include(self.class.instance_method(:m2).inspect, "(a, b)")
assert_include(self.class.instance_method(:mo1).inspect, "(a=..., &b)")
assert_include(self.class.instance_method(:mo2).inspect, "(a, b=...)")
assert_include(self.class.instance_method(:mo3).inspect, "(*a)")
assert_include(self.class.instance_method(:mo4).inspect, "(a, *b, &c)")
assert_include(self.class.instance_method(:mo5).inspect, "(a, *b, c)")
assert_include(self.class.instance_method(:mo6).inspect, "(a, *b, c, &d)")
assert_include(self.class.instance_method(:mo7).inspect, "(a, b=..., *c, d, &e)")
assert_include(self.class.instance_method(:mo8).inspect, "(a, b=..., *, d, &e)")
assert_include(self.class.instance_method(:ma1).inspect, "(_, &b)")
assert_include(self.class.instance_method(:mk1).inspect, "(**)")
assert_include(self.class.instance_method(:mk2).inspect, "(**o)")
assert_include(self.class.instance_method(:mk3).inspect, "(a, **o)")
assert_include(self.class.instance_method(:mk4).inspect, "(a=..., **o)")
assert_include(self.class.instance_method(:mk5).inspect, "(a, b=..., **o)")
assert_include(self.class.instance_method(:mk6).inspect, "(a, b=..., c, **o)")
assert_include(self.class.instance_method(:mk7).inspect, "(a, b=..., *c, d, **o)")
assert_include(self.class.instance_method(:mk8).inspect, "(a, b=..., *c, d, e:, f: ..., **o)")
assert_include(self.class.instance_method(:mnk).inspect, "(**nil)")
assert_include(self.class.instance_method(:mf).inspect, "(...)")
end
def test_public_method_with_zsuper_method
c = Class.new
c.class_eval do
def foo
:ok
end
private :foo
end
d = Class.new(c)
d.class_eval do
public :foo
end
assert_equal(:ok, d.new.public_method(:foo).call)
end
def test_public_methods_with_extended
m = Module.new do def m1; end end
a = Class.new do def a; end end
bug = '[ruby-dev:41553]'
obj = a.new
assert_equal([:a], obj.public_methods(false), bug)
obj.extend(m)
assert_equal([:m1, :a], obj.public_methods(false), bug)
end
def test_visibility
assert_equal('method', defined?(mv1))
assert_equal('method', defined?(mv2))
assert_equal('method', defined?(mv3))
assert_equal('method', defined?(self.mv1))
assert_equal(nil, defined?(self.mv2))
assert_equal('method', defined?(self.mv3))
assert_equal(true, respond_to?(:mv1))
assert_equal(false, respond_to?(:mv2))
assert_equal(false, respond_to?(:mv3))
assert_equal(true, respond_to?(:mv1, true))
assert_equal(true, respond_to?(:mv2, true))
assert_equal(true, respond_to?(:mv3, true))
assert_nothing_raised { mv1 }
assert_nothing_raised { mv2 }
assert_nothing_raised { mv3 }
assert_nothing_raised { self.mv1 }
assert_nothing_raised { self.mv2 }
assert_raise(NoMethodError) { (self).mv2 }
assert_nothing_raised { self.mv3 }
class << (obj = Object.new)
private def [](x) x end
def mv1(x) self[x] end
def mv2(x) (self)[x] end
end
assert_nothing_raised { obj.mv1(0) }
assert_raise(NoMethodError) { obj.mv2(0) }
v = Visibility.new
assert_equal('method', defined?(v.mv1))
assert_equal(nil, defined?(v.mv2))
assert_equal(nil, defined?(v.mv3))
assert_equal(true, v.respond_to?(:mv1))
assert_equal(false, v.respond_to?(:mv2))
assert_equal(false, v.respond_to?(:mv3))
assert_equal(true, v.respond_to?(:mv1, true))
assert_equal(true, v.respond_to?(:mv2, true))
assert_equal(true, v.respond_to?(:mv3, true))
assert_nothing_raised { v.mv1 }
assert_raise(NoMethodError) { v.mv2 }
assert_raise(NoMethodError) { v.mv3 }
assert_nothing_raised { v.__send__(:mv1) }
assert_nothing_raised { v.__send__(:mv2) }
assert_nothing_raised { v.__send__(:mv3) }
assert_nothing_raised { v.instance_eval { mv1 } }
assert_nothing_raised { v.instance_eval { mv2 } }
assert_nothing_raised { v.instance_eval { mv3 } }
end
def test_bound_method_entry
bug6171 = '[ruby-core:43383]'
assert_ruby_status([], <<-EOC, bug6171)
class Bug6171
def initialize(target)
define_singleton_method(:reverse, target.method(:reverse).to_proc)
end
end
100.times {p = Bug6171.new('test'); 1000.times {p.reverse}}
EOC
end
def test_unbound_method_proc_coerce
# '&' coercion of an UnboundMethod raises TypeError
assert_raise(TypeError) do
Class.new do
define_method('foo', &Object.instance_method(:to_s))
end
end
end
def test___dir__
assert_instance_of String, __dir__
assert_equal(File.dirname(File.realpath(__FILE__)), __dir__)
bug8436 = '[ruby-core:55123] [Bug #8436]'
file, line = *binding.source_location
file = File.realpath(file)
assert_equal(__dir__, eval("__dir__", binding, file, line), bug8436)
bug8662 = '[ruby-core:56099] [Bug #8662]'
assert_equal("arbitrary", eval("__dir__", binding, "arbitrary/file.rb"), bug8662)
assert_equal("arbitrary", Object.new.instance_eval("__dir__", "arbitrary/file.rb"), bug8662)
end
def test_alias_owner
bug7613 = '[ruby-core:51105]'
bug7993 = '[Bug #7993]'
c = Class.new {
def foo
end
prepend Module.new
attr_reader :zot
}
x = c.new
class << x
alias bar foo
end
assert_equal(c, c.instance_method(:foo).owner)
assert_equal(c, x.method(:foo).owner)
assert_equal(x.singleton_class, x.method(:bar).owner)
assert_equal(x.method(:foo), x.method(:bar), bug7613)
assert_equal(c, x.method(:zot).owner, bug7993)
assert_equal(c, c.instance_method(:zot).owner, bug7993)
end
def test_included
m = Module.new {
def foo
end
}
c = Class.new {
def foo
end
include m
}
assert_equal(c, c.instance_method(:foo).owner)
end
def test_prepended
bug7836 = '[ruby-core:52160] [Bug #7836]'
bug7988 = '[ruby-core:53038] [Bug #7988]'
m = Module.new {
def foo
end
}
c = Class.new {
def foo
end
prepend m
}
assert_raise(NameError, bug7988) {Module.new{prepend m}.instance_method(:bar)}
true || c || bug7836
end
def test_gced_bmethod
assert_normal_exit %q{
require 'irb'
IRB::Irb.module_eval do
define_method(:eval_input) do
IRB::Irb.module_eval { alias_method :eval_input, :to_s }
GC.start
Kernel
end
end
IRB.start
}, '[Bug #7825]'
end
def test_singleton_method
feature8391 = '[ruby-core:54914] [Feature #8391]'
c1 = Class.new
c1.class_eval { def foo; :foo; end }
o = c1.new
def o.bar; :bar; end
assert_nothing_raised(NameError) {o.method(:foo)}
assert_raise(NameError, feature8391) {o.singleton_method(:foo)}
m = assert_nothing_raised(NameError, feature8391) {break o.singleton_method(:bar)}
assert_equal(:bar, m.call, feature8391)
end
def test_singleton_method_prepend
bug14658 = '[Bug #14658]'
c1 = Class.new
o = c1.new
def o.bar; :bar; end
class << o; prepend Module.new; end
m = assert_nothing_raised(NameError, bug14658) {o.singleton_method(:bar)}
assert_equal(:bar, m.call, bug14658)
o = Object.new
assert_raise(NameError, bug14658) {o.singleton_method(:bar)}
end
Feature9783 = '[ruby-core:62212] [Feature #9783]'
def assert_curry_three_args(m)
curried = m.curry
assert_equal(6, curried.(1).(2).(3), Feature9783)
curried = m.curry(3)
assert_equal(6, curried.(1).(2).(3), Feature9783)
assert_raise_with_message(ArgumentError, /wrong number/) {m.curry(2)}
end
def test_curry_method
c = Class.new {
def three_args(a,b,c) a + b + c end
}
assert_curry_three_args(c.new.method(:three_args))
end
def test_curry_from_proc
c = Class.new {
define_method(:three_args) {|x,y,z| x + y + z}
}
assert_curry_three_args(c.new.method(:three_args))
end
def assert_curry_var_args(m)
curried = m.curry(3)
assert_equal([1, 2, 3], curried.(1).(2).(3), Feature9783)
curried = m.curry(2)
assert_equal([1, 2], curried.(1).(2), Feature9783)
curried = m.curry(0)
assert_equal([1], curried.(1), Feature9783)
end
def test_curry_var_args
c = Class.new {
def var_args(*args) args end
}
assert_curry_var_args(c.new.method(:var_args))
end
def test_curry_from_proc_var_args
c = Class.new {
define_method(:var_args) {|*args| args}
}
assert_curry_var_args(c.new.method(:var_args))
end
Feature9781 = '[ruby-core:62202] [Feature #9781]'
def test_super_method
o = Derived.new
m = o.method(:foo).super_method
assert_equal(Base, m.owner, Feature9781)
assert_same(o, m.receiver, Feature9781)
assert_equal(:foo, m.name, Feature9781)
m = assert_nothing_raised(NameError, Feature9781) {break m.super_method}
assert_nil(m, Feature9781)
end
def test_super_method_unbound
m = Derived.instance_method(:foo)
m = m.super_method
assert_equal(Base.instance_method(:foo), m, Feature9781)
m = assert_nothing_raised(NameError, Feature9781) {break m.super_method}
assert_nil(m, Feature9781)
bug11419 = '[ruby-core:70254]'
m = Object.instance_method(:tap)
m = assert_nothing_raised(NameError, bug11419) {break m.super_method}
assert_nil(m, bug11419)
end
def test_super_method_module
m1 = Module.new {def foo; end}
c1 = Class.new(Derived) {include m1; def foo; end}
m = c1.instance_method(:foo)
assert_equal(c1, m.owner, Feature9781)
m = m.super_method
assert_equal(m1, m.owner, Feature9781)
m = m.super_method
assert_equal(Derived, m.owner, Feature9781)
m = m.super_method
assert_equal(Base, m.owner, Feature9781)
m2 = Module.new {def foo; end}
o = c1.new.extend(m2)
m = o.method(:foo)
assert_equal(m2, m.owner, Feature9781)
m = m.super_method
assert_equal(c1, m.owner, Feature9781)
assert_same(o, m.receiver, Feature9781)
c1 = Class.new {def foo; end}
c2 = Class.new(c1) {include m1; include m2}
m = c2.instance_method(:foo)
assert_equal(m2, m.owner)
m = m.super_method
assert_equal(m1, m.owner)
m = m.super_method
assert_equal(c1, m.owner)
assert_nil(m.super_method)
end
def test_super_method_bind_unbind_clone
bug15629_m1 = Module.new do
def foo; end
end
bug15629_m2 = Module.new do
def foo; end
end
bug15629_c = Class.new do
include bug15629_m1
include bug15629_m2
end
o = bug15629_c.new
m = o.method(:foo)
sm = m.super_method
im = bug15629_c.instance_method(:foo)
sim = im.super_method
assert_equal(sm, m.clone.super_method)
assert_equal(sim, m.unbind.super_method)
assert_equal(sim, m.unbind.clone.super_method)
assert_equal(sim, im.clone.super_method)
assert_equal(sm, m.unbind.bind(o).super_method)
assert_equal(sm, m.unbind.clone.bind(o).super_method)
assert_equal(sm, im.bind(o).super_method)
assert_equal(sm, im.clone.bind(o).super_method)
end
def test_super_method_removed_public
c1 = Class.new {private def foo; end}
c2 = Class.new(c1) {public :foo}
c3 = Class.new(c2) {def foo; end}
c1.class_eval {undef foo}
m = c3.instance_method(:foo)
m = assert_nothing_raised(NameError, Feature9781) {break m.super_method}
assert_nil(m, Feature9781)
end
def test_super_method_removed_regular
c1 = Class.new { def foo; end }
c2 = Class.new(c1) { def foo; end }
assert_equal c1.instance_method(:foo), c2.instance_method(:foo).super_method
c1.remove_method :foo
assert_equal nil, c2.instance_method(:foo).super_method
end
def test_prepended_public_zsuper
mod = EnvUtil.labeled_module("Mod") {private def foo; [:ok] end}
obj = Object.new.extend(mod)
class << obj
public :foo
end
mod1 = EnvUtil.labeled_module("Mod1") {def foo; [:mod1] + super end}
obj.singleton_class.prepend(mod1)
mod2 = EnvUtil.labeled_module("Mod2") {def foo; [:mod2] + super end}
obj.singleton_class.prepend(mod2)
m = obj.method(:foo)
assert_equal mod2, m.owner
assert_equal mod1, m.super_method.owner
assert_equal obj.singleton_class, m.super_method.super_method.owner
assert_equal nil, m.super_method.super_method.super_method
assert_equal [:mod2, :mod1, :ok], obj.foo
end
def test_super_method_with_prepended_module
bug = '[ruby-core:81666] [Bug #13656] should be the method of the parent'
c1 = EnvUtil.labeled_class("C1") {def m; end}
c2 = EnvUtil.labeled_class("C2", c1) {def m; end}
c2.prepend(EnvUtil.labeled_module("M"))
m1 = c1.instance_method(:m)
m2 = c2.instance_method(:m).super_method
assert_equal(m1, m2, bug)
assert_equal(c1, m2.owner, bug)
assert_equal(m1.source_location, m2.source_location, bug)
end
def test_super_method_after_bind
assert_nil String.instance_method(:length).bind(String.new).super_method,
'[ruby-core:85231] [Bug #14421]'
end
def test_super_method_alias
c0 = Class.new do
def m1
[:C0_m1]
end
def m2
[:C0_m2]
end
end
c1 = Class.new(c0) do
def m1
[:C1_m1] + super
end
alias m2 m1
end
c2 = Class.new(c1) do
def m2
[:C2_m2] + super
end
end
o1 = c2.new
assert_equal([:C2_m2, :C1_m1, :C0_m1], o1.m2)
m = o1.method(:m2)
assert_equal([:C2_m2, :C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:C0_m1], m.call)
assert_nil(m.super_method)
end
def test_super_method_alias_to_prepended_module
m = Module.new do
def m1
[:P_m1] + super
end
def m2
[:P_m2] + super
end
end
c0 = Class.new do
def m1
[:C0_m1]
end
end
c1 = Class.new(c0) do
def m1
[:C1_m1] + super
end
prepend m
alias m2 m1
end
o1 = c1.new
assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], o1.m2)
m = o1.method(:m2)
assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:P_m1, :C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:C1_m1, :C0_m1], m.call)
m = m.super_method
assert_equal([:C0_m1], m.call)
assert_nil(m.super_method)
end
# Bug 17780
def test_super_method_module_alias
m = Module.new do
def foo
end
alias :f :foo
end
method = m.instance_method(:f)
super_method = method.super_method
assert_nil(super_method)
end
# Bug 18435
def test_instance_methods_owner_consistency
a = Module.new { def method1; end }
b = Class.new do
include a
protected :method1
end
assert_equal [:method1], b.instance_methods(false)
assert_equal b, b.instance_method(:method1).owner
end
def test_zsuper_method_removed
a = EnvUtil.labeled_class('A') do
private
def foo(arg = nil)
1
end
end
line = __LINE__ - 4
b = EnvUtil.labeled_class('B', a) do
public :foo
end
unbound = b.instance_method(:foo)
assert_equal unbound, b.public_instance_method(:foo)
assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
assert_equal [[:opt, :arg]], unbound.parameters
a.remove_method(:foo)
assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
assert_equal [[:opt, :arg]], unbound.parameters
obj = b.new
assert_equal 1, unbound.bind_call(obj)
assert_include b.instance_methods(false), :foo
link = 'https://github.com/ruby/ruby/pull/6467#issuecomment-1262159088'
assert_raise(NameError, link) { b.instance_method(:foo) }
# For #test_method_list below, otherwise we get the same error as just above
b.remove_method(:foo)
end
def test_zsuper_method_removed_higher_method
a0 = EnvUtil.labeled_class('A0') do
def foo(arg1 = nil, arg2 = nil)
0
end
end
line0 = __LINE__ - 4
a0_foo = a0.instance_method(:foo)
a = EnvUtil.labeled_class('A', a0) do
private
def foo(arg = nil)
1
end
end
line = __LINE__ - 4
b = EnvUtil.labeled_class('B', a) do
public :foo
end
unbound = b.instance_method(:foo)
assert_equal a0_foo, unbound.super_method
a.remove_method(:foo)
assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
assert_equal [[:opt, :arg]], unbound.parameters
assert_equal a0_foo, unbound.super_method
obj = b.new
assert_equal 1, unbound.bind_call(obj)
assert_include b.instance_methods(false), :foo
assert_equal "#<UnboundMethod: A0#foo(arg1=..., arg2=...) #{__FILE__}:#{line0}>", b.instance_method(:foo).inspect
end
def test_zsuper_method_redefined_bind_call
c0 = EnvUtil.labeled_class('C0') do
def foo
[:foo]
end
end
c1 = EnvUtil.labeled_class('C1', c0) do
def foo
super + [:bar]
end
end
m1 = c1.instance_method(:foo)
c2 = EnvUtil.labeled_class('C2', c1) do
private :foo
end
assert_equal [:foo], c2.private_instance_methods(false)
m2 = c2.instance_method(:foo)
c1.class_exec do
remove_method :foo
def foo
[:bar2]
end
end
m3 = c2.instance_method(:foo)
c = c2.new
assert_equal [:foo, :bar], m1.bind_call(c)
assert_equal c1, m1.owner
assert_equal [:foo, :bar], m2.bind_call(c)
assert_equal c2, m2.owner
assert_equal [:bar2], m3.bind_call(c)
assert_equal c2, m3.owner
end
# Bug #18751
def method_equality_visbility_alias
c = Class.new do
class << self
alias_method :n, :new
private :new
end
end
assert_equal c.method(:n), c.method(:new)
assert_not_equal c.method(:n), Class.method(:new)
assert_equal c.method(:n) == Class.instance_method(:new).bind(c)
assert_not_equal c.method(:new), Class.method(:new)
assert_equal c.method(:new), Class.instance_method(:new).bind(c)
end
def rest_parameter(*rest)
rest
end
def test_splat_long_array
if File.exist?('/etc/os-release') && File.read('/etc/os-release').include?('openSUSE Leap')
# For RubyCI's openSUSE machine http://rubyci.s3.amazonaws.com/opensuseleap/ruby-trunk/recent.html, which tends to die with NoMemoryError here.
omit 'do not exhaust memory on RubyCI openSUSE Leap machine'
end
n = 10_000_000
assert_equal n , rest_parameter(*(1..n)).size, '[Feature #10440]'
end
class C
D = "Const_D"
def foo
a = b = c = a = b = c = 12345
end
end
def test_to_proc_binding
bug11012 = '[ruby-core:68673] [Bug #11012]'
b = C.new.method(:foo).to_proc.binding
assert_equal([], b.local_variables, bug11012)
assert_equal("Const_D", b.eval("D"), bug11012) # Check CREF
assert_raise(NameError, bug11012){ b.local_variable_get(:foo) }
assert_equal(123, b.local_variable_set(:foo, 123), bug11012)
assert_equal(123, b.local_variable_get(:foo), bug11012)
assert_equal(456, b.local_variable_set(:bar, 456), bug11012)
assert_equal(123, b.local_variable_get(:foo), bug11012)
assert_equal(456, b.local_variable_get(:bar), bug11012)
assert_equal([:bar, :foo], b.local_variables.sort, bug11012)
end
MethodInMethodClass_Setup = -> do
remove_const :MethodInMethodClass if defined? MethodInMethodClass
class MethodInMethodClass
def m1
def m2
end
self.class.send(:define_method, :m3){} # [Bug #11754]
end
private
end
end
def test_method_in_method_visibility_should_be_public
MethodInMethodClass_Setup.call
assert_equal([:m1].sort, MethodInMethodClass.public_instance_methods(false).sort)
assert_equal([].sort, MethodInMethodClass.private_instance_methods(false).sort)
MethodInMethodClass.new.m1
assert_equal([:m1, :m2, :m3].sort, MethodInMethodClass.public_instance_methods(false).sort)
assert_equal([].sort, MethodInMethodClass.private_instance_methods(false).sort)
end
def test_define_method_with_symbol
assert_normal_exit %q{
define_method(:foo, &:to_s)
define_method(:bar, :to_s.to_proc)
}, '[Bug #11850]'
c = Class.new{
define_method(:foo, &:to_s)
define_method(:bar, :to_s.to_proc)
}
obj = c.new
assert_equal('1', obj.foo(1))
assert_equal('1', obj.bar(1))
end
def test_argument_error_location
body = <<~'END_OF_BODY'
eval <<~'EOS', nil, "main.rb"
$line_lambda = __LINE__; $f = lambda do
_x = 1
end
$line_method = __LINE__; def foo
_x = 1
end
begin
$f.call(1)
rescue ArgumentError => e
assert_equal "main.rb:#{$line_lambda}:in 'block in <main>'", e.backtrace.first
end
begin
foo(1)
rescue ArgumentError => e
assert_equal "main.rb:#{$line_method}:in 'foo'", e.backtrace.first
end
EOS
END_OF_BODY
assert_separately [], body
# without trace insn
assert_separately [], "RubyVM::InstructionSequence.compile_option = {trace_instruction: false}\n" + body
end
def test_zsuper_private_override_instance_method
assert_separately([], <<-'end;', timeout: 30)
# Bug #16942 [ruby-core:98691]
module M
def x
end
end
module M2
prepend Module.new
include M
private :x
end
::Object.prepend(M2)
m = Object.instance_method(:x)
assert_equal M2, m.owner
end;
end
def test_override_optimized_method_on_class_using_prepend
assert_separately([], <<-'end;', timeout: 30)
# Bug #17725 [ruby-core:102884]
$VERBOSE = nil
String.prepend(Module.new)
class String
def + other
'blah blah'
end
end
assert_equal('blah blah', 'a' + 'b')
end;
end
def test_eqq
assert_operator(0.method(:<), :===, 5)
assert_not_operator(0.method(:<), :===, -5)
end
def test_compose_with_method
c = Class.new {
def f(x) x * 2 end
def g(x) x + 1 end
}
f = c.new.method(:f)
g = c.new.method(:g)
assert_equal(6, (f << g).call(2))
assert_equal(6, (g >> f).call(2))
end
def test_compose_with_proc
c = Class.new {
def f(x) x * 2 end
}
f = c.new.method(:f)
g = proc {|x| x + 1}
assert_equal(6, (f << g).call(2))
assert_equal(6, (g >> f).call(2))
end
def test_compose_with_callable
c = Class.new {
def f(x) x * 2 end
}
c2 = Class.new {
def call(x) x + 1 end
}
f = c.new.method(:f)
g = c2.new
assert_equal(6, (f << g).call(2))
assert_equal(5, (f >> g).call(2))
end
def test_compose_with_noncallable
c = Class.new {
def f(x) x * 2 end
}
f = c.new.method(:f)
assert_raise(TypeError) {
f << 5
}
assert_raise(TypeError) {
f >> 5
}
end
def test_umethod_bind_call
foo = Base.instance_method(:foo)
assert_equal(:base, foo.bind_call(Base.new))
assert_equal(:base, foo.bind_call(Derived.new))
plus = Integer.instance_method(:+)
assert_equal(3, plus.bind_call(1, 2))
end
def test_method_list
# chkbuild lists all methods.
# The following code emulate this listing.
# use_symbol = Object.instance_methods[0].is_a?(Symbol)
nummodule = nummethod = 0
mods = []
ObjectSpace.each_object(Module) {|m| mods << m if String === m.name }
mods = mods.sort_by {|m| m.name }
mods.each {|mod|
nummodule += 1
mc = mod.kind_of?(Class) ? "class" : "module"
puts_line = "#{mc} #{mod.name} #{(mod.ancestors - [mod]).inspect}"
puts_line = puts_line # prevent unused var warning
mod.singleton_methods(false).sort.each {|methname|
nummethod += 1
meth = mod.method(methname)
line = "#{mod.name}.#{methname} #{meth.arity}"
line << " not-implemented" if !mod.respond_to?(methname)
# puts line
}
ms = mod.instance_methods(false)
if true or use_symbol
ms << :initialize if mod.private_instance_methods(false).include? :initialize
else
ms << "initialize" if mod.private_instance_methods(false).include? "initialize"
end
ms.sort.each {|methname|
nummethod += 1
meth = mod.instance_method(methname)
line = "#{mod.name}\##{methname} #{meth.arity}"
line << " not-implemented" if /\(not-implemented\)/ =~ meth.inspect
# puts line
}
}
# puts "#{nummodule} modules, #{nummethod} methods"
assert_operator nummodule, :>, 0
assert_operator nummethod, :>, 0
end
def test_invalidating_CC_ASAN
assert_ruby_status(['-e', 'using Module.new'])
end
def test_kwarg_eval_memory_leak
assert_no_memory_leak([], "", <<~RUBY, rss: true, limit: 1.2)
100_000.times do
eval("Hash.new(foo: 123)")
end
RUBY
end
def test_super_with_splat
c = Class.new {
attr_reader :x
def initialize(*args)
@x, _ = args
end
}
b = Class.new(c) { def initialize(...) = super }
a = Class.new(b) { def initialize(*args) = super }
obj = a.new(1, 2, 3)
assert_equal 1, obj.x
end
def test_warn_unused_block
assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
def foo = nil
foo{} # warn
send(:foo){} # warn
b = Proc.new{}
foo(&b) # warn
RUBY
assert_equal 3, err.size
err = err.join
assert_match(/-:2: warning/, err)
assert_match(/-:3: warning/, err)
assert_match(/-:5: warning/, err)
end
assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
def foo = nil
10.times{foo{}} # warn once
RUBY
assert_equal 1, err.size
end
assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
def foo = nil; b = nil
foo(&b) # no warning
1.object_id{} # no warning because it is written in C
class C
def initialize
end
end
C.new{} # no warning
RUBY
assert_equal 0, err.size
end
assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
class C0
def f1 = nil
def f2 = nil
def f3 = nil
def f4 = nil
def f5 = nil
def f6 = nil
end
class C1 < C0
def f1 = super # zsuper / use
def f2 = super() # super / use
def f3(&_) = super(&_) # super / use
def f4 = super(&nil) # super / unuse
def f5 = super(){} # super / unuse
def f6 = super{} # zsuper / unuse
end
C1.new.f1{} # no warning
C1.new.f2{} # no warning
C1.new.f3{} # no warning
C1.new.f4{} # warning
C1.new.f5{} # warning
C1.new.f6{} # warning
RUBY
assert_equal 3, err.size, err.join("\n")
assert_match(/-:22: warning.+f4/, err.join)
assert_match(/-:23: warning.+f5/, err.join)
assert_match(/-:24: warning.+f6/, err.join)
end
assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status|
class C0
def f = yield
end
class C1 < C0
def f = nil
end
C1.new.f{} # do not warn on duck typing
RUBY
assert_equal 0, err.size, err.join("\n")
end
end
end