[前][次][番号順一覧][スレッド一覧]

ruby-changes:57902

From: Jeremy <ko1@a...>
Date: Thu, 26 Sep 2019 04:34:11 +0900 (JST)
Subject: [ruby-changes:57902] 3b302ea8c9 (master): Add Module#ruby2_keywords for passing keywords through regular argument splats

https://git.ruby-lang.org/ruby.git/commit/?id=3b302ea8c9

From 3b302ea8c95d34d5ef072d7e3b326f28a611e479 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@j...>
Date: Sat, 21 Sep 2019 09:03:36 -0700
Subject: Add Module#ruby2_keywords for passing keywords through regular
 argument splats

This approach uses a flag bit on the final hash object in the regular splat,
as opposed to a previous approach that used a VM frame flag.  The hash flag
approach is less invasive, and handles some cases that the VM frame flag
approach does not, such as saving the argument splat array and splatting it
later:

  ruby2_keywords def foo(*args)
    @args = args
    bar
  end
  def bar
    baz(*@args)
  end
  def baz(*args, **kw)
    [args, kw]
  end
  foo(a:1)    #=> [[], {a: 1}]
  foo({a: 1}, **{}) #=> [[{a: 1}], {}]

  foo({a: 1}) #=> 2.7: [[], {a: 1}] # and warning
  foo({a: 1}) #=> 3.0: [[{a: 1}], {}]

It doesn't handle some cases that the VM frame flag handles, such as when
the final hash object is replaced using Hash#merge, but those cases are
probably less common and are unlikely to properly support keyword
argument separation.

Use ruby2_keywords to handle argument delegation in the delegate library.

diff --git a/internal.h b/internal.h
index bb298d2..7de0077 100644
--- a/internal.h
+++ b/internal.h
@@ -815,6 +815,7 @@ struct RComplex { https://github.com/ruby/ruby/blob/trunk/internal.h#L815
 #define RCOMPLEX_SET_IMAG(cmp, i) RB_OBJ_WRITE((cmp), &((struct RComplex *)(cmp))->imag,(i))
 
 enum ruby_rhash_flags {
+    RHASH_PASS_AS_KEYWORDS = FL_USER1,                                   /* FL 1 */
     RHASH_PROC_DEFAULT = FL_USER2,                                       /* FL 2 */
     RHASH_ST_TABLE_FLAG = FL_USER3,                                      /* FL 3 */
 #define RHASH_AR_TABLE_MAX_SIZE SIZEOF_VALUE
diff --git a/lib/delegate.rb b/lib/delegate.rb
index 03ebfdd..a1589ec 100644
--- a/lib/delegate.rb
+++ b/lib/delegate.rb
@@ -75,7 +75,7 @@ class Delegator < BasicObject https://github.com/ruby/ruby/blob/trunk/lib/delegate.rb#L75
   #
   # Handles the magic of delegation through \_\_getobj\_\_.
   #
-  def method_missing(m, *args, &block)
+  ruby2_keywords def method_missing(m, *args, &block)
     r = true
     target = self.__getobj__ {r = false}
 
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 1dbde80..1cfa982 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -2306,6 +2306,278 @@ class TestKeywordArguments < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_keyword.rb#L2306
     assert_raise(ArgumentError) { m.call(42, a: 1, **h2) }
   end
 
+  def test_ruby2_keywords
+    c = Class.new do
+      ruby2_keywords def foo(meth, *args)
+        send(meth, *args)
+      end
+
+      ruby2_keywords def foo_bar(*args)
+        bar(*args)
+      end
+
+      ruby2_keywords def foo_baz(*args)
+        baz(*args)
+      end
+
+      ruby2_keywords def foo_mod(meth, *args)
+        args << 1
+        send(meth, *args)
+      end
+
+      ruby2_keywords def foo_bar_mod(*args)
+        args << 1
+        bar(*args)
+      end
+
+      ruby2_keywords def foo_baz_mod(*args)
+        args << 1
+        baz(*args)
+      end
+
+      def bar(*args, **kw)
+        [args, kw]
+      end
+
+      def baz(*args)
+        args
+      end
+
+      ruby2_keywords def foo_dbar(*args)
+        dbar(*args)
+      end
+
+      ruby2_keywords def foo_dbaz(*args)
+        dbaz(*args)
+      end
+
+      define_method(:dbar) do |*args, **kw|
+        [args, kw]
+      end
+
+      define_method(:dbaz) do |*args|
+        args
+      end
+
+      ruby2_keywords def block(*args)
+        ->(*args, **kw){[args, kw]}.(*args)
+      end
+
+      ruby2_keywords def cfunc(*args)
+        self.class.new(*args).init_args
+      end
+
+      ruby2_keywords def store_foo(meth, *args)
+        @stored_args = args
+        use(meth)
+      end
+      def use(meth)
+        send(meth, *@stored_args)
+      end
+
+      attr_reader :init_args
+      def initialize(*args, **kw)
+        @init_args = [args, kw]
+      end
+    end
+
+    mmkw = Class.new do
+      def method_missing(*args, **kw)
+        [args, kw]
+      end
+    end
+
+    mmnokw = Class.new do
+      def method_missing(*args)
+        args
+      end
+    end
+
+    implicit_super = Class.new(c) do
+      ruby2_keywords def bar(*args)
+        super
+      end
+
+      ruby2_keywords def baz(*args)
+        super
+      end
+    end
+
+    explicit_super = Class.new(c) do
+      ruby2_keywords def bar(*args)
+        super(*args)
+      end
+
+      ruby2_keywords def baz(*args)
+        super(*args)
+      end
+    end
+
+    h1 = {a: 1}
+    o = c.new
+
+    assert_equal([[1], h1], o.foo(:bar, 1, :a=>1))
+    assert_equal([1, h1], o.foo(:baz, 1, :a=>1))
+    assert_equal([[1], h1], o.store_foo(:bar, 1, :a=>1))
+    assert_equal([1, h1], o.store_foo(:baz, 1, :a=>1))
+    assert_equal([[1], h1], o.foo_bar(1, :a=>1))
+    assert_equal([1, h1], o.foo_baz(1, :a=>1))
+
+    assert_equal([[1], h1], o.foo(:bar, 1, **h1))
+    assert_equal([1, h1], o.foo(:baz, 1, **h1))
+    assert_equal([[1], h1], o.store_foo(:bar, 1, **h1))
+    assert_equal([1, h1], o.store_foo(:baz, 1, **h1))
+    assert_equal([[1], h1], o.foo_bar(1, **h1))
+    assert_equal([1, h1], o.foo_baz(1, **h1))
+
+    assert_equal([[h1], {}], o.foo(:bar, h1, **{}))
+    assert_equal([h1], o.foo(:baz, h1, **{}))
+    assert_equal([[h1], {}], o.store_foo(:bar, h1, **{}))
+    assert_equal([h1], o.store_foo(:baz, h1, **{}))
+    assert_equal([[h1], {}], o.foo_bar(h1, **{}))
+    assert_equal([h1], o.foo_baz(h1, **{}))
+
+    assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
+      assert_equal([[1], h1], o.foo(:bar, 1, h1))
+    end
+    assert_equal([1, h1], o.foo(:baz, 1, h1))
+    assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
+      assert_equal([[1], h1], o.store_foo(:bar, 1, h1))
+    end
+    assert_equal([1, h1], o.store_foo(:baz, 1, h1))
+    assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
+      assert_equal([[1], h1], o.foo_bar(1, h1))
+    end
+    assert_equal([1, h1], o.foo_baz(1, h1))
+
+    assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, :a=>1))
+    assert_equal([1, h1, 1], o.foo_mod(:baz, 1, :a=>1))
+    assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, :a=>1))
+    assert_equal([1, h1, 1], o.foo_baz_mod(1, :a=>1))
+
+    assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, **h1))
+    assert_equal([1, h1, 1], o.foo_mod(:baz, 1, **h1))
+    assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, **h1))
+    assert_equal([1, h1, 1], o.foo_baz_mod(1, **h1))
+
+    assert_equal([[h1, {}, 1], {}], o.foo_mod(:bar, h1, **{}))
+    assert_equal([h1, {}, 1], o.foo_mod(:baz, h1, **{}))
+    assert_equal([[h1, {}, 1], {}], o.foo_bar_mod(h1, **{}))
+    assert_equal([h1, {}, 1], o.foo_baz_mod(h1, **{}))
+
+    assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, h1))
+    assert_equal([1, h1, 1], o.foo_mod(:baz, 1, h1))
+    assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, h1))
+    assert_equal([1, h1, 1], o.foo_baz_mod(1, h1))
+
+    assert_equal([[1], h1], o.foo(:dbar, 1, :a=>1))
+    assert_equal([1, h1], o.foo(:dbaz, 1, :a=>1))
+    assert_equal([[1], h1], o.store_foo(:dbar, 1, :a=>1))
+    assert_equal([1, h1], o.store_foo(:dbaz, 1, :a=>1))
+    assert_equal([[1], h1], o.foo_dbar(1, :a=>1))
+    assert_equal([1, h1], o.foo_dbaz(1, :a=>1))
+
+    assert_equal([[1], h1], o.foo(:dbar, 1, **h1))
+    assert_equal([1, h1], o.foo(:dbaz, 1, **h1))
+    assert_equal([[1], h1], o.store_foo(:dbar, 1, **h1))
+    assert_equal([1, h1], o.store_foo(:dbaz, 1, **h1))
+    assert_equal([[1], h1], o.foo_dbar(1, **h1))
+    assert_equal([1, h1], o.foo_dbaz(1, **h1))
+
+    assert_equal([[h1], {}], o.foo(:dbar, h1, **{}))
+    assert_equal([h1], o.foo(:dbaz, h1, **{}))
+    assert_equal([[h1], {}], o.store_foo(:dbar, h1, **{}))
+    assert_equal([h1], o.store_foo(:dbaz, h1, **{}))
+    assert_equal([[h1], {}], o.foo_dbar(h1, **{}))
+    assert_equal([h1], o.foo_dbaz(h1, **{}))
+
+    assert_warn(/The last argument is used as the keyword parameter.* for method/m) do
+      assert_equal([[1], h1], o.foo(:dbar, 1, h1))
+    end
+    assert_equal([1, h1], o.foo(:dbaz, 1, h1))
+    assert_warn(/The last argument is used as the keyword parameter.* for method/m) do
+      assert_equal([[1], h1], o.store_foo(:dbar, 1, h1))
+    end
+    assert_equal([1, h1], o.store_foo(:dbaz, 1, h1))
+    assert_warn(/The last argument is used as the keyword parameter.* for method/m) do
+      assert_equal([[1], h1], o.foo_dbar(1, h1))
+    end
+    assert_equal([1, h1], o.foo_dbaz(1, h1))
+
+    assert_equal([[1], h1], o.block(1, :a=>1))
+    assert_equal([[1], h1], o.block(1, **h1))
+    assert_warn(/The last argument is used as the keyword parameter.* for `call'/m) do
+      assert_equal([[1], h1], o.block(1, h1))
+    end
+    assert_equal([[h1], {}], o.block(h1, **{}))
+
+    assert_equal([[1], h1], o.cfunc(1, :a=>1))
+    assert_equal([[1], h1], o.cfunc(1, **h1))
+    assert_warn(/The last argument is used as the keyword parameter.* for `initialize'/m) do
+      assert_equal([[1], h1], o.cfunc(1, h1))
+    end
+    assert_equal([[h1], {}], o.cfunc(h1, **{}))
+
+    o = mmkw.new
+    assert_equal([[:b, 1], h1], o.b(1, :a=>1))
+    assert_equal([[:b, 1], h1], o.b(1, **h1))
+    assert_warn(/The last argument is used as the keyword parameter.* for `method_missing'/m) do
+      assert_equal([[:b, 1], h1], o.b(1, h1))
+    end
+    assert_equal([[:b, h1], {}], o.b(h1, **{}))
+
+    o = mmnokw.new
+    assert_equal([:b, 1, h1], o.b(1, :a=>1))
+    assert_equal([:b, 1, h1], o.b(1, **h1))
+    assert_equal([:b, 1, h1], o.b(1, h1))
+    assert_equal([:b, h1], o.b(h1, **{}))
+
+    o = implicit_super.new
+    assert_equal([[1], h1], o.bar(1, :a=>1))
+    assert_equal([[1], h1], o.bar(1, **h1))
+    assert_warn(/The last argument is used as the keyword parameter.* fo (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]