diff --git a/mrbgems/mruby-hash-ext/mrblib/hash.rb b/mrbgems/mruby-hash-ext/mrblib/hash.rb index 9ba326bc2..97e1031f9 100644 --- a/mrbgems/mruby-hash-ext/mrblib/hash.rb +++ b/mrbgems/mruby-hash-ext/mrblib/hash.rb @@ -32,13 +32,9 @@ class Hash other = others[i] i += 1 raise TypeError, "Hash required (#{other.class} given)" unless Hash === other - if block - other.each_key{|k| - self[k] = (self.has_key?(k))? block.call(k, self[k], other[k]): other[k] - } - else - other.each_key{|k| self[k] = other[k]} - end + other.each_key{|k| + self[k] = (self.has_key?(k))? block.call(k, self[k], other[k]): other[k] + } end self end diff --git a/mrbgems/mruby-hash-ext/src/hash_ext.c b/mrbgems/mruby-hash-ext/src/hash_ext.c index 26b042007..5b990d936 100644 --- a/mrbgems/mruby-hash-ext/src/hash_ext.c +++ b/mrbgems/mruby-hash-ext/src/hash_ext.c @@ -295,6 +295,44 @@ hash_deconstruct_keys(mrb_state *mrb, mrb_value hash) return result; } +/* + * call-seq: + * hsh.__merge(*others) -> hsh + * + * Merges multiple hashes into hsh. This is an internal method + * used by merge! for non-block cases. + * + * Raises ArgumentError if no arguments given. + * Raises TypeError if any argument is not a Hash. + * + * h = { a: 1, b: 2 } + * h.__merge({ c: 3 }, { d: 4 }) #=> { a: 1, b: 2, c: 3, d: 4 } + */ +static mrb_value +hash_merge(mrb_state *mrb, mrb_value hash) +{ + const mrb_value *argv; + mrb_int argc; + + mrb_get_args(mrb, "*", &argv, &argc); + + /* Validate arguments */ + if (argc == 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong number of arguments (given 0, expected 1+)"); + } + + /* Merge multiple hashes in C */ + for (mrb_int i = 0; i < argc; i++) { + if (!mrb_hash_p(argv[i])) { + mrb_raisef(mrb, E_TYPE_ERROR, "no implicit conversion of %C into Hash", + mrb_obj_class(mrb, argv[i])); + } + mrb_hash_merge(mrb, hash, argv[i]); + } + + return hash; +} + void mrb_mruby_hash_ext_gem_init(mrb_state *mrb) { @@ -306,6 +344,7 @@ mrb_mruby_hash_ext_gem_init(mrb_state *mrb) mrb_define_method_id(mrb, h, MRB_SYM(except), hash_except, MRB_ARGS_ANY()); mrb_define_method_id(mrb, h, MRB_SYM(key), hash_key, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, h, MRB_SYM(deconstruct_keys), hash_deconstruct_keys, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, h, MRB_SYM(__merge), hash_merge, MRB_ARGS_ANY()); mrb_define_class_method_id(mrb, h, MRB_OPSYM(aref), hash_s_create, MRB_ARGS_ANY()); } diff --git a/mrbgems/mruby-hash-ext/test/hash.rb b/mrbgems/mruby-hash-ext/test/hash.rb index 32d4561d9..30014e946 100644 --- a/mrbgems/mruby-hash-ext/test/hash.rb +++ b/mrbgems/mruby-hash-ext/test/hash.rb @@ -46,27 +46,27 @@ assert('Hash.[] for sub class') do end assert('Hash#merge!') do + # Single hash merge a = { 'abc_key' => 'abc_value', 'cba_key' => 'cba_value' } b = { 'cba_key' => 'XXX', 'xyz_key' => 'xyz_value' } - result_1 = a.merge! b + assert_equal({'abc_key' => 'abc_value', 'cba_key' => 'XXX', + 'xyz_key' => 'xyz_value' }, result_1) + # Block handling a = { 'abc_key' => 'abc_value', 'cba_key' => 'cba_value' } result_2 = a.merge!(b) do |key, original, new| original end - - assert_equal({'abc_key' => 'abc_value', 'cba_key' => 'XXX', - 'xyz_key' => 'xyz_value' }, result_1) assert_equal({'abc_key' => 'abc_value', 'cba_key' => 'cba_value', 'xyz_key' => 'xyz_value' }, result_2) - assert_raise(TypeError) do - { 'abc_key' => 'abc_value' }.merge! "a" - end - - # multiple arguments + # Multiple arguments assert_equal({a:1,b:2,c:3}, {a:1}.merge!({b:2},{c:3})) + + # Error cases + assert_raise(ArgumentError) { {}.merge!() } + assert_raise(TypeError) { {}.merge!("not a hash") } end assert('Hash#values_at') do