This is an OpenPGP/MIME signed message (RFC 2440 and 3156)
--------------enig569D2110070C43D21FBA54E1
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable

Hello,

I got a pull request for a HashWithIndifferentAccess implementation to me=
rge
into core.  I think it's almost OK to make it so (except few minor bugs t=
hat can
easily be handled).  How about it?  I'm also attaching a patch file for p=
eople who
are not used to git (mainly nobu).

-------- Original Message --------
Subject: [GitHub] methodmissing sent you a message
Date: Mon, 2 Nov 2009 12:40:24 -0800
From: GitHub <noreply / github.com>
To: shyouhei / ruby-lang.org



methodmissing wants you to pull from methodmissing/ruby at hwia

Body: Hi,

I've played extensively with a HashWithIndifferentAccess (hwia / Mash)
implementation as an extension to both ruby 1.9.x and ruby 1.8.7 (
http://github.com/methodmissing/hwia and
http://blog.methodmissing.com/2009/08/29/alternative-hashwithindifferenta=
ccess/)
before and recently took the leap to patch against 1.9.2 as a Hash subcla=
ss.

I've tried to conform to recommended code conventions, tried to not injec=
t this
into the critical path of common Hash use cases and abstracted to macros =
where
possible.

Wycats suggested to send the pull request - naming conventions at the mom=
ent is
dangling - StrHash, IndifferentHash etc. I'll make myself available with
whatever time is required to have this conform to current ruby-core
standards.I'm also open to moving the recursive conversion to an extensio=
n and
implement a thin API as per identhash on Hash instead.

It passes against today's (Nov 2) trunk with "make check" and related.Fee=
dback
much appreciated if and when there's a moment.

- Lourens

View repository: http://github.com/methodmissing/ruby/tree/hwia

-------- patch --------

% git diff --patch-with-stat origin/trunk..HEAD
 hash.c                   |  185 +++++++++++++++++++++++++++++++--
 include/ruby/intern.h    |    1 +
 string.c                 |   12 ++
 test/ruby/test_hash.rb   |  252 ++++++++++++++++++++++++++++++++++++++++=
++++++
 test/ruby/test_symbol.rb |    7 ++
 5 files changed, 445 insertions(+), 12 deletions(-)

diff --git a/hash.c b/hash.c
index ef8b9a8..332f89d 100644
--- a/hash.c
+++ b/hash.c
@@ -23,6 +23,27 @@ static VALUE rb_hash_s_try_convert(VALUE, VALUE);
=20
 #define HASH_DELETED  FL_USER1
 #define HASH_PROC_DEFAULT FL_USER2
+#define STR_HASH FL_USER3
+
+static VALUE rb_hash_rehash(VALUE hash);
+static VALUE rb_hash_update(VALUE hash1, VALUE hash2);
+static VALUE rb_hash_strhash(VALUE hash);
+static void rb_strhash_convert(VALUE *value);
+
+#define NEW_STR_HASH(hash,other) do {\
+   FL_SET(RHASH(hash), STR_HASH); \
+   RHASH(hash)->ntbl->type =3D &strhash; \
+   RHASH(hash)->ifnone =3D RHASH(other)->ifnone; \
+   return rb_hash_rehash(rb_convert_type(hash, T_HASH, "StrHash", "to_ha=
sh")); \
+} while (0)
+
+#define STR_HASH_P(hash) (FL_TEST(RHASH(hash), STR_HASH) || RBASIC(hash)=
->klass =3D=3D rb_cStrHash)
+
+#define CONVERT_STR_HASH(hash,value) do {\
+   if STR_HASH_P(hash){ \
+   rb_strhash_convert(&value); \
+   } \
+} while (0)
=20
 VALUE
 rb_hash_freeze(VALUE hash)
@@ -31,6 +52,7 @@ rb_hash_freeze(VALUE hash)
 }
=20
 VALUE rb_cHash;
+VALUE rb_cStrHash;
=20
 static VALUE envtbl;
 static ID id_hash, id_yield, id_default;
@@ -54,6 +76,37 @@ rb_any_cmp(VALUE a, VALUE b)
     return !rb_eql(a, b);
 }
=20
+int
+strhash_cmp(VALUE *s1,VALUE *s2)
+{
+    int s1_hash =3D SYMBOL_P(*s1) ? rb_sym_hash(*s1) : rb_str_hash(*s1);=

+    int s2_hash =3D SYMBOL_P(*s2) ? rb_sym_hash(*s2) : rb_str_hash(*s2);=

+    if (s1_hash =3D=3D s2_hash) return 0;
+    if (s1_hash > s2_hash) return 1;
+    return -1;=09
+}
+
+static int
+rb_strhash_cmp(VALUE a, VALUE b)
+{
+    if (a =3D=3D b) return 0;
+    if (FIXNUM_P(a) && FIXNUM_P(b)) {
+	return a !=3D b;
+    }
+    if (a =3D=3D Qundef || b =3D=3D Qundef) return -1;
+    if (SYMBOL_P(a) && SYMBOL_P(b)) {
+	return a !=3D b;
+    }
+    if ((TYPE(a) =3D=3D T_STRING && RBASIC(a)->klass =3D=3D rb_cString &=
& SYMBOL_P(b)) || (TYPE(b) =3D=3D T_STRING && RBASIC(b)->klass =3D=3D rb_=
cString && SYMBOL_P(a))) {
+	return strhash_cmp(&a, &b);
+    }
+    if (TYPE(a) =3D=3D T_STRING && RBASIC(a)->klass =3D=3D rb_cString &&=

+    TYPE(b) =3D=3D T_STRING && RBASIC(b)->klass =3D=3D rb_cString) {
+	return rb_str_cmp(a, b);
+    }
+    return !rb_eql(a, b);
+}
+
 VALUE
 rb_hash(VALUE obj)
 {
@@ -99,11 +152,44 @@ rb_any_hash(VALUE a)
     return (st_index_t)RSHIFT(hnum, 1);
 }
=20
+static st_index_t
+rb_strhash_hash(VALUE a)
+{
+    VALUE hval;
+    st_index_t hnum;
+
+    switch (TYPE(a)) {
+      case T_FIXNUM:
+      case T_NIL:
+      case T_FALSE:
+      case T_TRUE:
+	hnum =3D rb_hash_end(rb_hash_start((unsigned int)a));
+	break;
+      case T_SYMBOL:
+	hnum =3D rb_sym_hash(a); =20
+	break;
+      case T_STRING:
+	hnum =3D rb_str_hash(a);
+	break;
+
+      default:
+        hval =3D rb_hash(a);
+	hnum =3D FIX2LONG(hval);
+    }
+    hnum <<=3D 1;
+    return (st_index_t)RSHIFT(hnum, 1);
+}
+
 static const struct st_hash_type objhash =3D {
     rb_any_cmp,
     rb_any_hash,
 };
=20
+static const struct st_hash_type strhash =3D {
+    rb_strhash_cmp,
+    rb_strhash_hash,
+};
+
 static const struct st_hash_type identhash =3D {
     st_numcmp,
     st_numhash,
@@ -219,7 +305,7 @@ hash_alloc(VALUE klass)
     OBJSETUP(hash, klass, T_HASH);
=20
     hash->ifnone =3D Qnil;
-
+    if (klass =3D=3D rb_cStrHash) FL_SET(hash, STR_HASH);
     return (VALUE)hash;
 }
=20
@@ -230,6 +316,12 @@ rb_hash_new(void)
 }
=20
 VALUE
+rb_strhash_new(void)
+{
+    return hash_alloc(rb_cStrHash);
+}
+
+VALUE
 rb_hash_dup(VALUE hash)
 {
     NEWOBJ(ret, struct RHash);
@@ -240,8 +332,33 @@ rb_hash_dup(VALUE hash)
     if (FL_TEST(hash, HASH_PROC_DEFAULT)) {
         FL_SET(ret, HASH_PROC_DEFAULT);
     }
-    ret->ifnone =3D RHASH(hash)->ifnone;
-    return (VALUE)ret;
+    if STR_HASH_P(hash){
+       NEW_STR_HASH(ret,hash);
+    }else{=20
+      ret->ifnone =3D RHASH(hash)->ifnone;
+      return (VALUE)ret;
+    }
+}
+
+static void
+rb_strhash_convert(VALUE *val)
+{
+    int i;
+    VALUE values;
+
+    switch (TYPE(*val)) {
+      case T_HASH:
+           *val =3D rb_hash_strhash(*val);=20
+           break;  =20
+      case T_ARRAY:
+            values =3D rb_ary_new2(RARRAY_LEN(*val));
+            for (i =3D 0; i < RARRAY_LEN(*val); i++) {
+               VALUE el =3D RARRAY_PTR(*val)[i];
+               rb_ary_push(values, (TYPE(el) =3D=3D T_HASH) ? rb_hash_st=
rhash(el) : el);
+            }=20
+           *val =3D values;
+           break;
+    }
 }
=20
 static void
@@ -256,7 +373,7 @@ struct st_table *
 rb_hash_tbl(VALUE hash)
 {
     if (!RHASH(hash)->ntbl) {
-        RHASH(hash)->ntbl =3D st_init_table(&objhash);
+	RHASH(hash)->ntbl =3D STR_HASH_P(hash) ? st_init_table(&strhash) : st_i=
nit_table(&objhash);
     }
     return RHASH(hash)->ntbl;
 }
@@ -318,7 +435,9 @@ static VALUE
 rb_hash_initialize(int argc, VALUE *argv, VALUE hash)
 {
     VALUE ifnone;
-
+    VALUE constructor;
+    rb_scan_args(argc, argv, "01", &constructor);
+    if(TYPE(constructor) =3D=3D T_HASH) return rb_hash_update(hash,const=
ructor);
     rb_hash_modify(hash);
     if (rb_block_given_p()) {
 	if (argc > 0) {
@@ -333,7 +452,6 @@ rb_hash_initialize(int argc, VALUE *argv, VALUE hash)=

 	rb_scan_args(argc, argv, "01", &ifnone);
 	RHASH(hash)->ifnone =3D ifnone;
     }
-
     return hash;
 }
=20
@@ -366,8 +484,11 @@ rb_hash_s_create(int argc, VALUE *argv, VALUE klass)=

 	    hash =3D hash_alloc(klass);
 	    if (RHASH(tmp)->ntbl) {
 		RHASH(hash)->ntbl =3D st_copy(RHASH(tmp)->ntbl);
+		if (FL_TEST(RHASH(tmp), STR_HASH) || klass =3D=3D rb_cStrHash){
+		  NEW_STR_HASH(hash,tmp);
+		}else
+		return hash;
 	    }
-	    return hash;
 	}
=20
 	tmp =3D rb_check_array_type(argv[0]);
@@ -426,12 +547,21 @@ rb_hash_s_try_convert(VALUE dummy, VALUE hash)
     return rb_check_convert_type(hash, T_HASH, "Hash", "to_hash");
 }
=20
+static VALUE
+rb_strhash_s_try_convert(VALUE dummy, VALUE hash)
+{
+    return rb_check_convert_type(hash, T_HASH, "StrHash", "to_hash");
+}
+
 static int
 rb_hash_rehash_i(VALUE key, VALUE value, VALUE arg)
 {
     st_table *tbl =3D (st_table *)arg;
=20
-    if (key !=3D Qundef) st_insert(tbl, key, value);
+    if (key !=3D Qundef){=20
+      if (tbl->type =3D=3D &strhash) rb_strhash_convert(&value);
+      st_insert(tbl, key, value);
+    }
     return ST_CONTINUE;
 }
=20
@@ -459,7 +589,6 @@ static VALUE
 rb_hash_rehash(VALUE hash)
 {
     st_table *tbl;
-
     if (RHASH(hash)->iter_lev > 0) {
 	rb_raise(rb_eRuntimeError, "rehash during iteration");
     }
@@ -470,7 +599,6 @@ rb_hash_rehash(VALUE hash)
     rb_hash_foreach(hash, rb_hash_rehash_i, (VALUE)tbl);
     st_free_table(RHASH(hash)->ntbl);
     RHASH(hash)->ntbl =3D tbl;
-
     return hash;
 }
=20
@@ -974,7 +1102,7 @@ rb_hash_select(VALUE hash)
     VALUE result;
=20
     RETURN_ENUMERATOR(hash, 0, 0);
-    result =3D rb_hash_new();
+    result =3D STR_HASH_P(hash) ? rb_strhash_new() : rb_hash_new();
     rb_hash_foreach(hash, select_i, result);
     return result;
 }
@@ -1046,6 +1174,13 @@ rb_hash_aset(VALUE hash, VALUE key, VALUE val)
     return val;
 }
=20
+VALUE
+rb_strhash_aset(VALUE hash, VALUE key, VALUE val)
+{
+    CONVERT_STR_HASH(hash,val);
+    rb_hash_aset(hash, key, val);
+}
+
 static int
 replace_i(VALUE key, VALUE val, VALUE hash)
 {
@@ -1617,7 +1752,7 @@ rb_hash_invert_i(VALUE key, VALUE value, VALUE hash=
)
 static VALUE
 rb_hash_invert(VALUE hash)
 {
-    VALUE h =3D rb_hash_new();
+	VALUE h =3D STR_HASH_P(hash) ? rb_strhash_new() : rb_hash_new();
=20
     rb_hash_foreach(hash, rb_hash_invert_i, h);
     return h;
@@ -1627,6 +1762,7 @@ static int
 rb_hash_update_i(VALUE key, VALUE value, VALUE hash)
 {
     if (key =3D=3D Qundef) return ST_CONTINUE;
+    CONVERT_STR_HASH(hash,value);
     st_insert(RHASH(hash)->ntbl, key, value);
     return ST_CONTINUE;
 }
@@ -1637,6 +1773,7 @@ rb_hash_update_block_i(VALUE key, VALUE value, VALU=
E hash)
     if (key =3D=3D Qundef) return ST_CONTINUE;
     if (rb_hash_has_key(hash, key)) {
 	value =3D rb_yield_values(3, key, rb_hash_aref(hash, key), value);
+    CONVERT_STR_HASH(hash,value);
     }
     st_insert(RHASH(hash)->ntbl, key, value);
     return ST_CONTINUE;
@@ -1852,6 +1989,24 @@ rb_hash_compare_by_id_p(VALUE hash)
     return Qfalse;
 }
=20
+static VALUE
+rb_hash_strhash(VALUE hash)
+{
+    if STR_HASH_P(hash)
+    return hash;
+    VALUE args[1];
+    args[0] =3D hash;
+    return rb_hash_s_create(1, (VALUE *)args, rb_cStrHash);
+}
+
+static VALUE
+rb_strhash_to_hash(VALUE hash)
+{
+    VALUE args[1];
+    args[0] =3D hash;
+    return rb_hash_s_create(1, (VALUE *)args, rb_cHash);
+}
+
 static int path_tainted =3D -1;
=20
 static char **origenviron;
@@ -2649,6 +2804,8 @@ Init_Hash(void)
     id_default =3D rb_intern("default");
=20
     rb_cHash =3D rb_define_class("Hash", rb_cObject);
+    rb_cStrHash =3D rb_define_class("StrHash", rb_cHash);
+    rb_define_singleton_method(rb_cStrHash, "try_convert", rb_strhash_s_=
try_convert, 1);
=20
     rb_include_module(rb_cHash, rb_mEnumerable);
=20
@@ -2715,6 +2872,10 @@ Init_Hash(void)
=20
     rb_define_method(rb_cHash,"compare_by_identity", rb_hash_compare_by_=
id, 0);
     rb_define_method(rb_cHash,"compare_by_identity?", rb_hash_compare_by=
_id_p, 0);
+    rb_define_method(rb_cHash, "strhash", rb_hash_strhash, 0);
+    rb_define_method(rb_cStrHash, "to_hash", rb_strhash_to_hash, 0);
+    rb_define_method(rb_cStrHash, "[]=3D", rb_strhash_aset, 2);
+    rb_define_method(rb_cStrHash, "store", rb_strhash_aset, 2);
=20
     origenviron =3D environ;
     envtbl =3D rb_obj_alloc(rb_cObject);
diff --git a/include/ruby/intern.h b/include/ruby/intern.h
index fd37eca..c97caef 100644
--- a/include/ruby/intern.h
+++ b/include/ruby/intern.h
@@ -660,6 +660,7 @@ void rb_str_associate(VALUE, VALUE);
 VALUE rb_str_associated(VALUE);
 void rb_str_setter(VALUE, ID, VALUE*);
 VALUE rb_str_intern(VALUE);
+st_index_t rb_sym_hash(VALUE);
 VALUE rb_sym_to_s(VALUE);
 VALUE rb_str_length(VALUE);
 long rb_str_offset(VALUE, long);
diff --git a/string.c b/string.c
index e7f7e86..cc4ff34 100644
--- a/string.c
+++ b/string.c
@@ -7025,6 +7025,17 @@ sym_equal(VALUE sym1, VALUE sym2)
     return Qfalse;
 }
=20
+st_index_t
+rb_sym_hash(VALUE sym){
+   ID id =3D SYM2ID(sym);
+   return rb_str_hash(rb_id2str(id)); =20
+}
+
+static VALUE
+rb_sym_hash_m(VALUE sym){
+    st_index_t hval =3D rb_sym_hash(sym);
+    return INT2FIX(hval);
+}
=20
 static int
 sym_printable(const char *s, const char *send, rb_encoding *enc)
@@ -7508,6 +7519,7 @@ Init_String(void)
     rb_define_method(rb_cSymbol, "=3D=3D=3D", sym_equal, 1);
     rb_define_method(rb_cSymbol, "inspect", sym_inspect, 0);
     rb_define_method(rb_cSymbol, "to_s", rb_sym_to_s, 0);
+    rb_define_method(rb_cSymbol, "hash", rb_sym_hash_m, 0);
     rb_define_method(rb_cSymbol, "id2name", rb_sym_to_s, 0);
     rb_define_method(rb_cSymbol, "intern", sym_to_sym, 0);
     rb_define_method(rb_cSymbol, "to_sym", sym_to_sym, 0);
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index c860b25..b3145e9 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -872,3 +872,255 @@ class TestHash < Test::Unit::TestCase
     assert_equal({x=3D>1}.hash, {x=3D>1}.hash)
   end
 end
+
+class HashToStrHash < Test::Unit::TestCase
+  def test_strhash
+    hash =3D { 'a' =3D> 1, 'b' =3D> 2 }
+    assert_instance_of StrHash, hash.strhash
+    assert_equal %w(a b), hash.keys
+    assert_equal [1,2], hash.values
+  end =20
+end
+
+class TestStrHash < Test::Unit::TestCase
+  def setup
+    @strings =3D { 'a' =3D> 1, 'b' =3D> 2 }.strhash
+    @symbols =3D { :a  =3D> 1, :b  =3D> 2 }.strhash
+    @mixed   =3D { :a  =3D> 1, 'b' =3D> 2 }.strhash
+    @fixnums =3D {  0  =3D> 1,  1  =3D> 2 }.strhash
+  end
+
+  def test_inherits_hash
+    assert_equal Hash, StrHash.superclass
+  end
+
+  def test_strhash
+    assert_equal @strings.object_id, @strings.strhash.object_id
+    assert_instance_of StrHash, { 'a' =3D> 1, 'b' =3D> 2 }.strhash
+  end
+ =20
+  def test_initialize
+    strhash =3D StrHash.new({ 'a' =3D> 1, 'b' =3D> 2 })
+    assert_equal 1, strhash[:a]=20
+    strhash =3D StrHash.new
+    strhash[:a] =3D 'a'
+    assert_equal 'a', strhash[:a]
+  end
+
+  def test_set
+    array =3D [{ 'a' =3D> 1, 'b' =3D> 2 }, [:a,:b,:c]]
+    @strings[:array] =3D array
+    assert_instance_of StrHash, @strings[:array].shift
+    assert_instance_of Array, @strings[:array] =3D array   =20
+    @strings[:hash] =3D { 'a' =3D> 1, 'b' =3D> 2 }
+    assert_instance_of StrHash, @strings[:hash]
+  end
+ =20
+  def test_dup
+    assert_equal @strings, @strings.dup
+    assert_equal @mixed, @mixed.dup
+    assert_not_equal @mixed.object_id, @mixed.dup.object_id
+  end
+
+  def test_keys
+    assert_equal ["a", "b"], @strings.keys
+    assert_equal [:a, :b], @symbols.keys
+    assert_equal [:a, "b"], @mixed.keys
+    assert_equal [0, 1], @fixnums.keys           =20
+  end
+ =20
+  def test_values
+    assert_equal [1, 2], @strings.values
+    assert_equal [1, 2], @symbols.values
+    assert_equal [1, 2], @mixed.values
+    assert_equal [1, 2], @fixnums.values        =20
+  end =20
+=20
+  def test_fetch
+    assert_equal 1, @strings.fetch('a')
+    assert_equal 1, @strings.fetch(:a.to_s)
+    assert_equal 1, @strings.fetch(:a)
+  end
+
+  def test_key?
+    assert @strings.key?(:a)
+    assert @strings.include?('a')
+    assert @mixed.has_key?('b')
+  end
+ =20
+  def test_delete
+    @strings.delete('a')
+    assert !@strings.key?(:a)
+  end
+
+  def test_assorted
+    hashes =3D { :@strings =3D> @strings, :@symbols =3D> @symbols, :@mix=
ed =3D> @mixed }
+    method_map =3D { :'[]' =3D> 1, :fetch =3D> 1, :values_at =3D> [1],
+      :has_key? =3D> true, :include? =3D> true, :key? =3D> true,
+      :member? =3D> true }
+
+    hashes.each do |name, hash|
+      method_map.sort_by { |m| m.to_s }.each do |meth, expected|
+        assert_equal(expected, hash.__send__(meth, 'a'),
+                     "Calling #{name}.#{meth} 'a'")
+        assert_equal(expected, hash.__send__(meth, :a),
+                     "Calling #{name}.#{meth} :a")
+      end
+    end
+
+    assert_equal [1, 2], @strings.values_at('a', 'b')
+    assert_equal [1, 2], @strings.values_at(:a, :b)
+    assert_equal [1, 2], @symbols.values_at('a', 'b')
+    assert_equal [1, 2], @symbols.values_at(:a, :b)
+    assert_equal [1, 2], @mixed.values_at('a', 'b')
+    assert_equal [1, 2], @mixed.values_at(:a, :b)
+  end
+ =20
+  def test_reading
+    hash =3D StrHash.new
+    hash["a"] =3D 1
+    hash["b"] =3D true
+    hash["c"] =3D false
+    hash["d"] =3D nil
+
+    assert_equal 1, hash[:a]
+    assert_equal true, hash[:b]
+    assert_equal false, hash[:c]
+    assert_equal nil, hash[:d]
+    assert_equal nil, hash[:e]
+  end =20
+
+  def test_reading_with_nonnil_default
+    hash =3D StrHash.new(1)
+    hash["a"] =3D 1
+    hash["b"] =3D true
+    hash["c"] =3D false
+    hash["d"] =3D nil
+
+    assert_equal 1, hash[:a]
+    assert_equal true, hash[:b]
+    assert_equal false, hash[:c]
+    assert_equal nil, hash[:d]
+    assert_equal 1, hash[:e]
+  end
+ =20
+  def test_writing
+    hash =3D StrHash.new
+    hash[:a] =3D 1
+    hash['b'] =3D 2
+    hash[3] =3D 3
+
+    assert_equal hash['a'], 1
+    assert_equal hash['b'], 2
+    assert_equal hash[:a], 1
+    assert_equal hash[:b], 2
+    assert_equal hash[3], 3
+  end =20
+
+  def test_update
+    hash =3D StrHash.new
+    hash[:a] =3D 'a'
+    hash['b'] =3D 'b'
+
+    updated_with_strings =3D hash.update(@strings)
+    updated_with_symbols =3D hash.update(@symbols)
+    updated_with_mixed =3D hash.update(@mixed)
+
+    assert_equal updated_with_strings[:a], 1
+    assert_equal updated_with_strings['a'], 1
+    assert_equal updated_with_strings['b'], 2
+
+    assert_equal updated_with_symbols[:a], 1
+    assert_equal updated_with_symbols['b'], 2
+    assert_equal updated_with_symbols[:b], 2
+
+    assert_equal updated_with_mixed[:a], 1
+    assert_equal updated_with_mixed['b'], 2
+
+    assert [updated_with_strings, updated_with_symbols, updated_with_mix=
ed].all? { |h| h.keys.size =3D=3D 2 }
+  end =20
+
+  def test_merging
+    hash =3D StrHash.new
+    hash[:a] =3D 'failure'
+    hash['b'] =3D 'failure'
+
+    other =3D { 'a' =3D> 1, :b =3D> 2 }
+
+    merged =3D hash.merge(other)
+
+    assert_equal StrHash, merged.class
+    assert_equal 1, merged[:a]
+    assert_equal 2, merged['b']
+
+    hash.update(other)
+
+    assert_equal 1, hash[:a]
+    assert_equal 2, hash['b']
+  end =20
+
+  def test_deleting
+    get_hash =3D proc{ StrHash[ :a =3D> 'foo' ] }
+    hash =3D get_hash.call
+    assert_equal hash.delete(:a), 'foo'
+    assert_equal hash.delete(:a), nil
+    hash =3D get_hash.call
+    assert_equal hash.delete('a'), 'foo'
+    assert_equal hash.delete('a'), nil
+  end =20
+
+  def test_to_hash
+    assert_instance_of Hash, @strings.to_hash
+    assert_equal %w(a b), @strings.to_hash.keys
+    # Should convert to a Hash with String keys.
+    assert_equal @strings, @mixed.strhash.to_hash
+
+    # Should preserve the default value.
+    mixed_with_default =3D @mixed.dup
+    mixed_with_default.default =3D '1234'
+    roundtrip =3D mixed_with_default.strhash.to_hash
+    assert_equal @strings, roundtrip
+    assert_equal '1234', roundtrip.default   =20
+  end
+
+  def test_hash_with_array_of_hashes
+    hash =3D { "urls" =3D> { "url" =3D> [ { "address" =3D> "1" }, { "add=
ress" =3D> "2" } ] }}
+    strhash =3D StrHash[hash]
+    assert_equal "1", strhash[:urls][:url].first[:address]
+  end
+
+  def test_indifferent_subhashes
+    h =3D {'user' =3D> {'id' =3D> 5}}.strhash
+    ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, =
h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
+
+    h =3D {:user =3D> {:id =3D> 5}}.strhash
+    ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, =
h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
+  end =20
+
+  def test_assorted_keys_not_stringified
+    original =3D {Object.new =3D> 2, 1 =3D> 2, [] =3D> true}
+    indiff =3D original.strhash
+    assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was convert=
ed to a string!")
+  end
+
+  def test_should_use_default_value_for_unknown_key
+    strhash =3D StrHash.new(3)
+    assert_equal 3, strhash[:new_key]
+  end
+
+  def test_should_use_default_value_if_no_key_is_supplied
+    strhash =3D StrHash.new(3)
+    assert_equal 3, strhash.default
+  end
+
+  def test_should_nil_if_no_default_value_is_supplied
+    strhash =3D StrHash.new
+    assert_nil strhash.default
+  end
+
+  def test_should_copy_the_default_value_when_converting_to_hash_with_in=
different_access
+    hash =3D Hash.new(3)
+    strhash =3D hash.strhash
+    assert_equal 3, strhash.default
+  end =20
+end
\ No newline at end of file
diff --git a/test/ruby/test_symbol.rb b/test/ruby/test_symbol.rb
index f402da3..c181b19 100644
--- a/test/ruby/test_symbol.rb
+++ b/test/ruby/test_symbol.rb
@@ -1,6 +1,13 @@
 require 'test/unit'
=20
 class TestSymbol < Test::Unit::TestCase
+  def test_hash
+    assert_instance_of Fixnum, :symbol.hash
+    assert_equal 'symbol'.hash, :symbol.hash
+    assert_equal :"!".hash, :"!".hash
+    assert_not_equal :"$1".hash, :"@@1".hash
+  end
+ =20
   # [ruby-core:3573]
=20
   def assert_eval_inspected(sym)


--------------enig569D2110070C43D21FBA54E1
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: OpenPGP digital signature
Content-Disposition: attachment; filename="signature.asc"

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkrwEAEACgkQuTXPUnA5eMLXsgCgg58U1ZGHQg9p/aQAO0Zc4R+q
ADgAn0+P43ISRIfrXYD7w5IhTRePCj0u
=mJaa
-----END PGP SIGNATURE-----

--------------enig569D2110070C43D21FBA54E1--