ruby-changes:52582
From: nobu <ko1@a...>
Date: Fri, 21 Sep 2018 00:07:02 +0900 (JST)
Subject: [ruby-changes:52582] nobu:r64794 (trunk): Enumerable#to_h with block and so on
nobu 2018-09-21 00:06:56 +0900 (Fri, 21 Sep 2018) New Revision: 64794 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=64794 Log: Enumerable#to_h with block and so on [Feature #15143] Modified files: trunk/NEWS trunk/array.c trunk/enum.c trunk/hash.c trunk/internal.h trunk/spec/ruby/core/array/to_h_spec.rb trunk/spec/ruby/core/enumerable/to_h_spec.rb trunk/spec/ruby/core/env/to_h_spec.rb trunk/spec/ruby/core/hash/to_h_spec.rb trunk/spec/ruby/core/struct/to_h_spec.rb trunk/struct.c trunk/test/ruby/test_array.rb trunk/test/ruby/test_enum.rb trunk/test/ruby/test_env.rb trunk/test/ruby/test_hash.rb trunk/test/ruby/test_struct.rb Index: test/ruby/test_hash.rb =================================================================== --- test/ruby/test_hash.rb (revision 64793) +++ test/ruby/test_hash.rb (revision 64794) @@ -847,6 +847,16 @@ class TestHash < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_hash.rb#L847 assert_equal("nope42", h[42]) end + def test_to_h_block + h = @h.to_h {|k, v| [k.to_s, v.to_s]} + assert_equal({ + "1"=>"one", "2"=>"two", "3"=>"three", to_s=>"self", + "true"=>"true", ""=>"nil", "nil"=>"" + }, + h) + assert_instance_of(Hash, h) + end + def test_nil_to_h h = nil.to_h assert_equal({}, h) Index: test/ruby/test_env.rb =================================================================== --- test/ruby/test_env.rb (revision 64793) +++ test/ruby/test_env.rb (revision 64794) @@ -399,6 +399,8 @@ class TestEnv < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_env.rb#L399 def test_to_h assert_equal(ENV.to_hash, ENV.to_h) + assert_equal(ENV.map {|k, v| ["$#{k}", v.size]}.to_h, + ENV.to_h {|k, v| ["$#{k}", v.size]}) end def test_reject Index: test/ruby/test_array.rb =================================================================== --- test/ruby/test_array.rb (revision 64793) +++ test/ruby/test_array.rb (revision 64794) @@ -1585,15 +1585,17 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L1585 $, = nil end + StubToH = [ + [:key, :value], + Object.new.tap do |kvp| + def kvp.to_ary + [:obtained, :via_to_ary] + end + end, + ] + def test_to_h - kvp = Object.new - def kvp.to_ary - [:obtained, :via_to_ary] - end - array = [ - [:key, :value], - kvp, - ] + array = StubToH assert_equal({key: :value, obtained: :via_to_ary}, array.to_h) e = assert_raise(TypeError) { @@ -1607,6 +1609,27 @@ class TestArray < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_array.rb#L1609 } assert_equal "wrong array length at 2 (expected 2, was 1)", e.message end + + def test_to_h_block + array = StubToH + assert_equal({"key" => "value", "obtained" => "via_to_ary"}, + array.to_h {|k, v| [k.to_s, v.to_s]}) + + assert_equal({first_one: :ok, not_ok: :ng}, + [[:first_one, :ok], :not_ok].to_h {|k, v| [k, v || :ng]}) + + e = assert_raise(TypeError) { + [[:first_one, :ok], :not_ok].to_h {|k, v| v ? [k, v] : k} + } + assert_equal "wrong element type Symbol at 1 (expected array)", e.message + array = [1] + k = eval("class C\u{1f5ff}; self; end").new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) {array.to_h {k}} + e = assert_raise(ArgumentError) { + [[:first_one, :ok], [1, 2], [:not_ok]].to_h {|kv| kv} + } + assert_equal "wrong array length at 2 (expected 2, was 1)", e.message + end def test_min assert_equal(1, [1, 2, 3, 1, 2].min) Index: test/ruby/test_struct.rb =================================================================== --- test/ruby/test_struct.rb (revision 64793) +++ test/ruby/test_struct.rb (revision 64794) @@ -362,6 +362,13 @@ module TestStruct https://github.com/ruby/ruby/blob/trunk/test/ruby/test_struct.rb#L362 assert_equal({a:1, b:2, c:3, d:4, e:5, f:6}, o.to_h) end + def test_to_h_block + klass = @Struct.new(:a, :b, :c, :d, :e, :f) + o = klass.new(1, 2, 3, 4, 5, 6) + assert_equal({"a" => 1, "b" => 4, "c" => 9, "d" => 16, "e" => 25, "f" => 36}, + o.to_h {|k, v| [k.to_s, v*v]}) + end + def test_question_mark_in_member klass = @Struct.new(:a, :b?) x = Object.new Index: test/ruby/test_enum.rb =================================================================== --- test/ruby/test_enum.rb (revision 64793) +++ test/ruby/test_enum.rb (revision 64794) @@ -144,8 +144,7 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L144 assert_equal([], inf.to_a) end - def test_to_h - obj = Object.new + StubToH = Object.new.tap do |obj| def obj.each(*args) yield(*args) yield [:key, :value] @@ -157,6 +156,12 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L156 yield kvp end obj.extend Enumerable + obj.freeze + end + + def test_to_h + obj = StubToH + assert_equal({ :hello => :world, :key => :value, @@ -174,6 +179,27 @@ class TestEnumerable < Test::Unit::TestC https://github.com/ruby/ruby/blob/trunk/test/ruby/test_enum.rb#L179 } assert_equal "element has wrong array length (expected 2, was 1)", e.message end + + def test_to_h_block + obj = StubToH + + assert_equal({ + "hello" => "world", + "key" => "value", + "other_key" => "other_value", + "obtained" => "via_to_ary", + }, obj.to_h(:hello, :world) {|k, v| [k.to_s, v.to_s]}) + + e = assert_raise(TypeError) { + obj.to_h {:not_an_array} + } + assert_equal "wrong element type Symbol (expected array)", e.message + + e = assert_raise(ArgumentError) { + obj.to_h {[1]} + } + assert_equal "element has wrong array length (expected 2, was 1)", e.message + end def test_inject assert_equal(12, @obj.inject {|z, x| z * x }) Index: struct.c =================================================================== --- struct.c (revision 64793) +++ struct.c (revision 64794) @@ -880,9 +880,14 @@ rb_struct_to_h(VALUE s) https://github.com/ruby/ruby/blob/trunk/struct.c#L880 VALUE h = rb_hash_new_with_size(RSTRUCT_LEN(s)); VALUE members = rb_struct_members(s); long i; + int block_given = rb_block_given_p(); for (i=0; i<RSTRUCT_LEN(s); i++) { - rb_hash_aset(h, rb_ary_entry(members, i), RSTRUCT_GET(s, i)); + VALUE k = rb_ary_entry(members, i), v = RSTRUCT_GET(s, i); + if (block_given) + rb_hash_set_pair(h, rb_yield_values(2, k, v)); + else + rb_hash_aset(h, k, v); } return h; } Index: spec/ruby/core/array/to_h_spec.rb =================================================================== --- spec/ruby/core/array/to_h_spec.rb (revision 64793) +++ spec/ruby/core/array/to_h_spec.rb (revision 64794) @@ -34,4 +34,11 @@ describe "Array#to_h" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/array/to_h_spec.rb#L34 it "does not accept arguments" do lambda { [].to_h(:a, :b) }.should raise_error(ArgumentError) end + + ruby_version_is "2.6" do + it "converts [key, value] pairs returned by the block to a hash" do + i = 0 + [:a, :b].to_h {|k| [k, i += 1]}.should == { a: 1, b: 2 } + end + end end Index: spec/ruby/core/enumerable/to_h_spec.rb =================================================================== --- spec/ruby/core/enumerable/to_h_spec.rb (revision 64793) +++ spec/ruby/core/enumerable/to_h_spec.rb (revision 64794) @@ -43,4 +43,12 @@ describe "Enumerable#to_h" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/enumerable/to_h_spec.rb#L43 enum = EnumerableSpecs::EachDefiner.new([:x]) lambda { enum.to_h }.should raise_error(ArgumentError) end + + ruby_version_is "2.6" do + it "converts [key, value] pairs returned by the block to a hash" do + enum = EnumerableSpecs::EachDefiner.new(:a, :b) + i = 0 + enum.to_h {|k| [k, i += 1]}.should == { a: 1, b: 2 } + end + end end Index: spec/ruby/core/struct/to_h_spec.rb =================================================================== --- spec/ruby/core/struct/to_h_spec.rb (revision 64793) +++ spec/ruby/core/struct/to_h_spec.rb (revision 64794) @@ -12,4 +12,12 @@ describe "Struct#to_h" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/struct/to_h_spec.rb#L12 car.to_h[:make] = 'Suzuki' car.make.should == 'Ford' end + + ruby_version_is "2.6" do + it "converts [key, value] pairs returned by the block to a hash" do + car = StructClasses::Car.new('Ford', 'Ranger') + h = car.to_h {|k, v| [k.to_s, "#{v}".downcase]} + h.should == {"make" => "ford", "model" => "ranger", "year" => ""} + end + end end Index: spec/ruby/core/env/to_h_spec.rb =================================================================== --- spec/ruby/core/env/to_h_spec.rb (revision 64793) +++ spec/ruby/core/env/to_h_spec.rb (revision 64794) @@ -3,4 +3,17 @@ require_relative 'shared/to_hash' https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/env/to_h_spec.rb#L3 describe "ENV.to_hash" do it_behaves_like :env_to_hash, :to_h + + ruby_version_is "2.6" do + it "converts [key, value] pairs returned by the block to a hash" do + orig = ENV.to_hash + begin + ENV.replace "a" => "b", "c" => "d" + i = 0 + ENV.to_h {|k, v| [k.to_sym, v.upcase]}.should == {a:"B", c:"D"} + ensure + ENV.replace orig + end + end + end end Index: spec/ruby/core/hash/to_h_spec.rb =================================================================== --- spec/ruby/core/hash/to_h_spec.rb (revision 64793) +++ spec/ruby/core/hash/to_h_spec.rb (revision 64794) @@ -7,6 +7,12 @@ describe "Hash#to_h" do https://github.com/ruby/ruby/blob/trunk/spec/ruby/core/hash/to_h_spec.rb#L7 h.to_h.should equal(h) end + ruby_version_is "2.6" do + it "converts [key, value] pairs returned by the block to a hash" do + {a: 1, b: 2}.to_h {|k, v| [k.to_s, v*v]}.should == { "a" => 1, "b" => 4 } + end + end + describe "when called on a subclass of Hash" do before :each do @h = HashSpecs::MyHash.new Index: array.c =================================================================== --- array.c (revision 64793) +++ array.c (revision 64794) @@ -2171,13 +2171,20 @@ rb_ary_to_a(VALUE ary) https://github.com/ruby/ruby/blob/trunk/array.c#L2171 /* * call-seq: - * ary.to_h -> hash + * ary.to_h -> hash + * ary.to_h { block } -> hash * * Returns the result of interpreting <i>ary</i> as an array of * <tt>[key, value]</tt> pairs. * * [[:foo, :bar], [1, 2]].to_h * # => {:foo => :bar, 1 => 2} + * + * If a block is given, the results of the block on each element of + * the array will be used as pairs. + * + * ["foo", "bar"].to_h {|s| [s.ord, s]} + * # => {102=>"foo", 98=>"bar"} */ static VALUE @@ -2185,8 +2192,11 @@ rb_ary_to_h(VALUE ary) https://github.com/ruby/ruby/blob/trunk/array.c#L2192 { long i; VALUE hash = rb_hash_new_with_size(RARRAY_LEN(ary)); + int block_given = rb_block_given_p(); + for (i=0; i<RARRAY_LEN(ary); i++) { - const VALUE elt = rb_ary_elt(ary, i); + const VALUE e = rb_ary_elt(ary, i); + const VALUE elt = block_given ? rb_yield_force_blockarg(e) : e; const VALUE key_value_pair = rb_check_array_type(elt); if (NIL_P(key_value_pair)) { rb_raise(rb_eTypeError, "wrong element type %"PRIsVALUE" at %ld (expected array)", Index: internal.h =================================================================== --- internal.h (revision 64793) +++ internal.h (revision 64794) @@ -1359,6 +1359,7 @@ VALUE rb_hash_values(VALUE hash); https://github.com/ruby/ruby/blob/trunk/internal.h#L1359 VALUE rb_hash_rehash(VALUE hash); int rb_hash_add_new_element(VALUE hash, VALUE key, VALUE val); #define HASH_PROC_DEFAULT FL_USER2 +VALUE rb_hash_set_pair(VALUE hash, VALUE pair); /* inits.c */ void rb_call_inits(void); Index: enum.c =================================================================== --- enum.c (revision 64793) +++ enum.c (revision 64794) @@ -613,38 +613,42 @@ enum_to_a(int argc, VALUE *argv, VALUE o https://github.com/ruby/ruby/blob/trunk/enum.c#L613 static VALUE enum_to_h_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash)) { - VALUE key_value_pair; ENUM_WANT_SVALUE(); rb_thread_check_ints(); - key_value_pair = rb_check_array_type(i); - if (NIL_P(key_value_pair)) { - rb_raise(rb_eTypeError, "wrong element type %s (expected array)", - rb_builtin_class_name(i)); - } - if (RARRAY_LEN(key_value_pair) != 2) { - rb_raise(rb_eArgError, "element has wrong array length (expected 2, was %ld)", - RARRAY_LEN(key_value_pair)); - } - rb_hash_aset(hash, RARRAY_AREF(key_value_pair, 0), RARRAY_AREF(key_value_pair, 1)); - return Qnil; + return rb_hash_set_pair(hash, i); +} + +static VALUE +enum_to_h_ii(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash)) +{ + rb_thread_check_ints(); + return rb_hash_set_pair(hash, rb_yield_values2(argc, argv)); } /* * call-seq: - * enum.to_h(*args) -> hash + * enum.to_h(*args) -> hash + * enum.to_h(*args) {...} -> hash * * Returns the result of interpreting <i>enum</i> as a list of * <tt>[key, value]</tt> pairs. * * %i[hello world].each_with_index.to_h * # => {:hello => 0, :world => 1} + * + * If a block is given, the results of the block on each element of + * the array will be used as pairs. + * + * (1..5).to_h {|x| [x, x ** 2]} + * #=> {1=>1, 2=>4, 3=>9, 4=>16, 5=>25} */ static VALUE enum_to_h(int argc, VALUE *argv, VALUE obj) { VALUE hash = rb_hash_new(); - rb_block_call(obj, id_each, argc, argv, enum_to_h_i, hash); + rb_block_call_func *iter = rb_block_given_p() ? enum_to_h_ii : enum_to_h_i; + rb_block_call(obj, id_each, argc, argv, iter, hash); OBJ_INFECT(hash, obj); return hash; } Index: NEWS =================================================================== --- NEWS (revision 64793) +++ NEWS (revision 64794) @@ -44,6 +44,11 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L44 * Added `Array#union` instance method. [Feature #14097] + * Modified methods: + + * `Array#to_h` now maps elements to new keys and values by the + block if given. [Feature #15143] + * Aliased methods: * `Array#filter` is a new alias for `Array#select`. @@ -74,6 +79,11 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L79 * `Enumerable` + * Modified methods: + + * `Enumerable#to_h` now maps elements to new keys and values + by the block if given. [Feature #15143] + * Aliased methods: * `Enumerable#filter` is a new alias for `Enumerable#select`. @@ -86,6 +96,13 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L96 * `Enumerator::Lazy#filter` is a new alias for `Enumerator::Lazy#select`. [Feature #13784] +* `ENV` + + * Modified methods: + + * `ENV.to_h` now maps names and values to new keys and values + by the block if given. [Feature #15143] + * `Exception` * New options: @@ -100,6 +117,9 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L117 * `Hash#merge`, `update`, `merge!` and `update!` now accept multiple arguments. [Feature #15111] + * `Hash#to_h` now maps keys and values to new keys and values + by the block if given. [Feature #15143] + * Aliased methods: * `Hash#filter` is a new alias for `Hash#select`. [Feature #13784] @@ -207,6 +227,11 @@ sufficient information, see the ChangeLo https://github.com/ruby/ruby/blob/trunk/NEWS#L227 * `Struct` + * Modified methods: + + * `Hash#to_h` now maps keys and values to new keys and values + by the block if given. [Feature #15143] + * Aliased method: * `Struct#filter` is a new alias for `Struct#select` [Feature #13784] Index: hash.c =================================================================== --- hash.c (revision 64793) +++ hash.c (revision 64794) @@ -2122,17 +2122,58 @@ rb_hash_to_hash(VALUE hash) https://github.com/ruby/ruby/blob/trunk/hash.c#L2122 return hash; } +VALUE +rb_hash_set_pair(VALUE hash, VALUE arg) +{ + VALUE pair; + + pair = rb_check_array_type(arg); + if (NIL_P(pair)) { + rb_raise(rb_eTypeError, "wrong element type %s (expected array)", + rb_builtin_class_name(arg)); + } + if (RARRAY_LEN(pair) != 2) { + rb_raise(rb_eArgError, "element has wrong array length (expected 2, was %ld)", + RARRAY_LEN(pair)); + } + rb_hash_aset(hash, RARRAY_AREF(pair, 0), RARRAY_AREF(pair, 1)); + return hash; +} + +static int +to_h_i(VALUE key, VALUE value, VALUE hash) +{ + rb_hash_set_pair(hash, rb_yield_values(2, key, value)); + return ST_CONTINUE; +} + +static VALUE +rb_hash_to_h_block(VALUE hash) +{ + VALUE h = rb_hash_new_with_size(RHASH_SIZE(hash)); + rb_hash_foreach(hash, to_h_i, h); + OBJ_INFECT(h, hash); + return h; +} + /* * call-seq: - * hsh.to_h -> hsh or new_hash + * hsh.to_h -> hsh or new_hash + * hsh.to_h {|key, value| block } -> new_hash * * Returns +self+. If called on a subclass of Hash, converts * the receiver to a Hash object. + * + * If a block is given, the results of the block on each pair of + * the receiver will be used as pairs. */ static VALUE rb_hash_to_h(VALUE hash) { + if (rb_block_given_p()) { + return rb_hash_to_h_block(hash); + } if (rb_obj_class(hash) != rb_cHash) { const VALUE flags = RBASIC(hash)->flags; hash = hash_dup(hash, rb_cHash, flags & HASH_PROC_DEFAULT); @@ -4460,7 +4501,6 @@ env_index(VALUE dmy, VALUE value) https://github.com/ruby/ruby/blob/trunk/hash.c#L4501 /* * call-seq: * ENV.to_hash -> hash - * ENV.to_h -> hash * * Creates a hash with a copy of the environment variables. * @@ -4487,6 +4527,24 @@ env_to_hash(void) https://github.com/ruby/ruby/blob/trunk/hash.c#L4527 /* * call-seq: + * ENV.to_h -> hash + * ENV.to_h {|name, value| block } -> hash + * + * Creates a hash with a copy of the environment variables. + * + */ +static VALUE +env_to_h(void) +{ + VALUE hash = env_to_hash(); + if (rb_block_given_p()) { + hash = rb_hash_to_h_block(hash); + } + return hash; +} + +/* + * call-seq: * ENV.reject { |name, value| } -> Hash * ENV.reject -> Enumerator * @@ -4873,7 +4931,7 @@ Init_Hash(void) https://github.com/ruby/ruby/blob/trunk/hash.c#L4931 rb_define_singleton_method(envtbl, "key?", env_has_key, 1); rb_define_singleton_method(envtbl, "value?", env_has_value, 1); rb_define_singleton_method(envtbl, "to_hash", env_to_hash, 0); - rb_define_singleton_method(envtbl, "to_h", env_to_hash, 0); + rb_define_singleton_method(envtbl, "to_h", env_to_h, 0); rb_define_singleton_method(envtbl, "assoc", env_assoc, 1); rb_define_singleton_method(envtbl, "rassoc", env_rassoc, 1); -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/