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/