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

ruby-changes:60435

From: Jeremy <ko1@a...>
Date: Wed, 18 Mar 2020 04:10:14 +0900 (JST)
Subject: [ruby-changes:60435] ac04b778c1 (master): Make {**{}} return unfrozen empty hash

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

From ac04b778c12120ab91986822b71edf16fea61465 Mon Sep 17 00:00:00 2001
From: Jeremy Evans <code@j...>
Date: Thu, 27 Feb 2020 11:15:04 -0800
Subject: Make {**{}} return unfrozen empty hash

Previously, method call keyword splats and hash keyword splats
were compiled exactly the same.  This is because parse-wise, they
operate on indentical nodes when it comes to compiling the **{}.

Fix this by using an ugly hack of temporarily modifying the
nd_brace flag in the method call keyword splat case.  Inside
compile_hash, only optimize the **{} case for hashes where the
nd_brace flag has been modified to reflect we are in the method
call keyword splat case and it is safe to do so.

Since compile_keyword_args is only called in one place, move the
keyword_node_p call out of that method to the single caller to
avoid duplicating the code.

diff --git a/compile.c b/compile.c
index 9c8f4dd..f55c76f 100644
--- a/compile.c
+++ b/compile.c
@@ -3898,10 +3898,14 @@ compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *co https://github.com/ruby/ruby/blob/trunk/compile.c#L3898
     return COMPILE_OK;
 }
 
+#define HASH_NO_BRACE 0
+#define HASH_BRACE 1
+#define METHOD_CALL_KEYWORDS 2
+
 static int
 keyword_node_p(const NODE *const node)
 {
-    return nd_type(node) == NODE_HASH && node->nd_brace == FALSE;
+    return nd_type(node) == NODE_HASH && (node->nd_brace & HASH_BRACE) != HASH_BRACE;
 }
 
 static int
@@ -3912,7 +3916,7 @@ compile_keyword_arg(rb_iseq_t *iseq, LINK_ANCHOR *const ret, https://github.com/ruby/ruby/blob/trunk/compile.c#L3916
 {
     if (kw_arg_ptr == NULL) return FALSE;
 
-    if (keyword_node_p(root_node) && root_node->nd_head && nd_type(root_node->nd_head) == NODE_LIST) {
+    if (root_node->nd_head && nd_type(root_node->nd_head) == NODE_LIST) {
 	const NODE *node = root_node->nd_head;
 
 	while (node) {
@@ -3970,9 +3974,18 @@ compile_args(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, https://github.com/ruby/ruby/blob/trunk/compile.c#L3974
             EXPECT_NODE("compile_args", node, NODE_LIST, -1);
         }
 
-        if (node->nd_next == NULL /* last node */ &&
-            compile_keyword_arg(iseq, ret, node->nd_head, keywords_ptr, flag)) {
-            len--;
+        if (node->nd_next == NULL && keyword_node_p(node->nd_head)) { /* last node */
+            if (compile_keyword_arg(iseq, ret, node->nd_head, keywords_ptr, flag)) {
+                len--;
+            }
+            else {
+                /* Bad Hack: temporarily mark hash node with flag so compile_hash
+                 * can compile call differently.
+                */
+                node->nd_head->nd_brace = METHOD_CALL_KEYWORDS;
+                NO_CHECK(COMPILE_(ret, "array element", node->nd_head, FALSE));
+                node->nd_head->nd_brace = HASH_NO_BRACE;
+            }
         }
         else {
             NO_CHECK(COMPILE_(ret, "array element", node->nd_head, FALSE));
@@ -4165,6 +4178,7 @@ static int https://github.com/ruby/ruby/blob/trunk/compile.c#L4178
 compile_hash(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int popped)
 {
     int line = (int)nd_line(node);
+    int method_call_keywords = node->nd_brace == METHOD_CALL_KEYWORDS;
 
     node = node->nd_head;
 
@@ -4297,7 +4311,7 @@ compile_hash(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int popp https://github.com/ruby/ruby/blob/trunk/compile.c#L4311
                 int only_kw = last_kw && first_kw;            /* foo(1,2,3, **kw) */
 
                 if (empty_kw) {
-                    if (only_kw) {
+                    if (only_kw && method_call_keywords) {
                         /* **{} appears at the last, so it won't be modified.
                          * kw is a special NODE_LIT that contains a special empty hash,
                          * so this emits: putobject {}
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb
index f92966f..a0c2bf6 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -93,6 +93,17 @@ class TestSyntax < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_syntax.rb#L93
     assert_valid_syntax("tap (proc do end)", __FILE__, bug9726)
   end
 
+  def test_hash_kwsplat_hash
+    kw = {}
+    h = {a: 1}
+    assert_equal({}, {**{}})
+    assert_equal({}, {**kw})
+    assert_equal(h, {**h})
+    assert_equal(false, {**{}}.frozen?)
+    assert_equal(false, {**kw}.equal?(kw))
+    assert_equal(false, {**h}.equal?(h))
+  end
+
   def test_array_kwsplat_hash
     kw = {}
     h = {a: 1}
-- 
cgit v0.10.2


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

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