Skip to content

Instantly share code, notes, and snippets.

@methodmissing
Created November 1, 2009 02:42
Show Gist options
  • Select an option

  • Save methodmissing/223370 to your computer and use it in GitHub Desktop.

Select an option

Save methodmissing/223370 to your computer and use it in GitHub Desktop.
methodmissing:ruby lourens$ ./miniruby -Ilib -rtest/unit test/ruby/test_hash.rb
Loaded suite test/ruby/test_hash
Started
.....E..................................................................EF.......F.....F...
Finished in 0.019967 seconds.
1) Error:
test_callcc(TestHash):
NoMethodError: undefined method `callcc' for #<TestHash:0x0000010101e9e0>
test/ruby/test_hash.rb:818:in `block in test_callcc'
test/ruby/test_hash.rb:818:in `each'
test/ruby/test_hash.rb:818:in `test_callcc'
2) Error:
test_hash_with_array_of_hashes(TestStrHash):
NoMethodError: undefined method `first' for nil:NilClass
test/ruby/test_hash.rb:1088:in `test_hash_with_array_of_hashes'
3) Failure:
test_indifferent_subhashes(TestStrHash) [test/ruby/test_hash.rb:1093]:
h["user"][:id] should be 5.
<5> expected but was
<nil>.
4) Failure:
test_set(TestStrHash) [test/ruby/test_hash.rb:915]:
Expected {"a"=>1, "b"=>2} to be an instance of StrHash, not Hash.
5) Failure:
test_to_hash(TestStrHash) [test/ruby/test_hash.rb:1072]:
Expected {"a"=>1, "b"=>2} to be an instance of Hash, not StrHash.
91 tests, 521 assertions, 3 failures, 2 errors, 0 skips
diff --git a/hash.c b/hash.c
index ef8b9a8..b6cd560 100644
--- a/hash.c
+++ b/hash.c
@@ -23,6 +23,25 @@ static VALUE rb_hash_s_try_convert(VALUE, VALUE);
#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 = &strhash; \
+ RHASH(hash)->ifnone = RHASH(other)->ifnone; \
+ return rb_hash_rehash(rb_convert_type(hash, T_HASH, "StrHash", "to_hash")); \
+} while (0)
+
+#define CONVERT_STR_HASH(hash,value) do {\
+ if FL_TEST(RHASH(hash), STR_HASH){ \
+ rb_strhash_convert(&value); \
+ } \
+} while (0)
VALUE
rb_hash_freeze(VALUE hash)
@@ -31,6 +50,7 @@ rb_hash_freeze(VALUE hash)
}
VALUE rb_cHash;
+VALUE rb_cStrHash;
static VALUE envtbl;
static ID id_hash, id_yield, id_default;
@@ -54,6 +74,37 @@ rb_any_cmp(VALUE a, VALUE b)
return !rb_eql(a, b);
}
+int
+strhash_cmp(VALUE *s1,VALUE *s2)
+{
+ int s1_hash = SYMBOL_P(*s1) ? rb_sym_hash(*s1) : rb_str_hash(*s1);
+ int s2_hash = SYMBOL_P(*s2) ? rb_sym_hash(*s2) : rb_str_hash(*s2);
+ if (s1_hash == s2_hash) return 0;
+ if (s1_hash > s2_hash) return 1;
+ return -1;
+}
+
+static int
+rb_strhash_cmp(VALUE a, VALUE b)
+{
+ if (a == b) return 0;
+ if (FIXNUM_P(a) && FIXNUM_P(b)) {
+ return a != b;
+ }
+ if (a == Qundef || b == Qundef) return -1;
+ if (SYMBOL_P(a) && SYMBOL_P(b)) {
+ return a != b;
+ }
+ if ((TYPE(a) == T_STRING && RBASIC(a)->klass == rb_cString && SYMBOL_P(b)) || (TYPE(b) == T_STRING && RBASIC(b)->klass == rb_cString && SYMBOL_P(a))) {
+ return strhash_cmp(&a, &b);
+ }
+ if (TYPE(a) == T_STRING && RBASIC(a)->klass == rb_cString &&
+ TYPE(b) == T_STRING && RBASIC(b)->klass == rb_cString) {
+ return rb_str_cmp(a, b);
+ }
+ return !rb_eql(a, b);
+}
+
VALUE
rb_hash(VALUE obj)
{
@@ -99,11 +150,44 @@ rb_any_hash(VALUE a)
return (st_index_t)RSHIFT(hnum, 1);
}
+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 = rb_hash_end(rb_hash_start((unsigned int)a));
+ break;
+ case T_SYMBOL:
+ hnum = rb_sym_hash(a);
+ break;
+ case T_STRING:
+ hnum = rb_str_hash(a);
+ break;
+
+ default:
+ hval = rb_hash(a);
+ hnum = FIX2LONG(hval);
+ }
+ hnum <<= 1;
+ return (st_index_t)RSHIFT(hnum, 1);
+}
+
static const struct st_hash_type objhash = {
rb_any_cmp,
rb_any_hash,
};
+static const struct st_hash_type strhash = {
+ rb_strhash_cmp,
+ rb_strhash_hash,
+};
+
static const struct st_hash_type identhash = {
st_numcmp,
st_numhash,
@@ -219,7 +303,9 @@ hash_alloc(VALUE klass)
OBJSETUP(hash, klass, T_HASH);
hash->ifnone = Qnil;
-
+ if (klass == rb_cStrHash){
+ FL_SET(hash, STR_HASH);
+ }
return (VALUE)hash;
}
@@ -230,6 +316,12 @@ rb_hash_new(void)
}
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 = RHASH(hash)->ifnone;
- return (VALUE)ret;
+ if (FL_TEST(hash, STR_HASH)){
+ NEW_STR_HASH(ret,hash);
+ }else{
+ ret->ifnone = RHASH(hash)->ifnone;
+ return (VALUE)ret;
+ }
+}
+
+static void
+rb_strhash_convert(VALUE *val)
+{
+ int i;
+ VALUE values;
+
+ switch (TYPE(*val)) {
+ case T_HASH:
+ *val = rb_hash_strhash(*val);
+ break;
+ case T_ARRAY:
+ values = rb_ary_new2(RARRAY_LEN(*val));
+ for (i = 0; i < RARRAY_LEN(*val); i++) {
+ VALUE el = RARRAY_PTR(*val)[i];
+ rb_ary_push(values, (TYPE(el) == T_HASH) ? rb_hash_strhash(el) : el);
+ }
+ *val = values;
+ break;
+ }
}
static void
@@ -256,7 +373,7 @@ struct st_table *
rb_hash_tbl(VALUE hash)
{
if (!RHASH(hash)->ntbl) {
- RHASH(hash)->ntbl = st_init_table(&objhash);
+ RHASH(hash)->ntbl = FL_TEST(hash, STR_HASH) ? st_init_table(&strhash) : st_init_table(&objhash);
}
return RHASH(hash)->ntbl;
}
@@ -318,7 +435,11 @@ 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) == T_HASH){
+ return rb_hash_update(hash,constructor);
+ }
rb_hash_modify(hash);
if (rb_block_given_p()) {
if (argc > 0) {
@@ -333,7 +454,6 @@ rb_hash_initialize(int argc, VALUE *argv, VALUE hash)
rb_scan_args(argc, argv, "01", &ifnone);
RHASH(hash)->ifnone = ifnone;
}
-
return hash;
}
@@ -366,8 +486,11 @@ rb_hash_s_create(int argc, VALUE *argv, VALUE klass)
hash = hash_alloc(klass);
if (RHASH(tmp)->ntbl) {
RHASH(hash)->ntbl = st_copy(RHASH(tmp)->ntbl);
+ if (FL_TEST(RHASH(tmp), STR_HASH) || klass == rb_cStrHash){
+ NEW_STR_HASH(hash,tmp);
+ }else
+ return hash;
}
- return hash;
}
tmp = rb_check_array_type(argv[0]);
@@ -406,7 +529,8 @@ rb_hash_s_create(int argc, VALUE *argv, VALUE klass)
static VALUE
to_hash(VALUE hash)
{
- return rb_convert_type(hash, T_HASH, "Hash", "to_hash");
+ char *type = FL_TEST(hash, STR_HASH) ? "StrHash" : "Hash";
+ return rb_convert_type(hash, T_HASH, type, "to_hash");
}
/*
@@ -423,7 +547,8 @@ to_hash(VALUE hash)
static VALUE
rb_hash_s_try_convert(VALUE dummy, VALUE hash)
{
- return rb_check_convert_type(hash, T_HASH, "Hash", "to_hash");
+ char *type = FL_TEST(hash, STR_HASH) ? "StrHash" : "Hash";
+ return rb_check_convert_type(hash, T_HASH, type, "to_hash");
}
static int
@@ -431,7 +556,12 @@ rb_hash_rehash_i(VALUE key, VALUE value, VALUE arg)
{
st_table *tbl = (st_table *)arg;
- if (key != Qundef) st_insert(tbl, key, value);
+ if (key != Qundef){
+ if (tbl == &strhash){
+ rb_strhash_convert(&value);
+ }
+ st_insert(tbl, key, value);
+ }
return ST_CONTINUE;
}
@@ -974,7 +1104,7 @@ rb_hash_select(VALUE hash)
VALUE result;
RETURN_ENUMERATOR(hash, 0, 0);
- result = rb_hash_new();
+ result = (FL_TEST(hash, STR_HASH)) ? rb_strhash_new() : rb_hash_new();
rb_hash_foreach(hash, select_i, result);
return result;
}
@@ -1034,6 +1164,7 @@ VALUE
rb_hash_aset(VALUE hash, VALUE key, VALUE val)
{
rb_hash_modify(hash);
+ CONVERT_STR_HASH(hash,val);
if (hash == key) {
rb_raise(rb_eArgError, "recursive key for hash");
}
@@ -1617,7 +1748,7 @@ rb_hash_invert_i(VALUE key, VALUE value, VALUE hash)
static VALUE
rb_hash_invert(VALUE hash)
{
- VALUE h = rb_hash_new();
+ VALUE h = (FL_TEST(hash, STR_HASH)) ? rb_strhash_new() : rb_hash_new();
rb_hash_foreach(hash, rb_hash_invert_i, h);
return h;
@@ -1627,6 +1758,7 @@ static int
rb_hash_update_i(VALUE key, VALUE value, VALUE hash)
{
if (key == Qundef) return ST_CONTINUE;
+ CONVERT_STR_HASH(hash,value);
st_insert(RHASH(hash)->ntbl, key, value);
return ST_CONTINUE;
}
@@ -1637,6 +1769,7 @@ rb_hash_update_block_i(VALUE key, VALUE value, VALUE hash)
if (key == Qundef) return ST_CONTINUE;
if (rb_hash_has_key(hash, key)) {
value = 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 +1985,14 @@ rb_hash_compare_by_id_p(VALUE hash)
return Qfalse;
}
+static VALUE
+rb_hash_strhash(VALUE hash)
+{
+ VALUE args[1];
+ args[0] = hash;
+ return rb_hash_s_create(1, (VALUE *)args, rb_cStrHash);
+}
+
static int path_tainted = -1;
static char **origenviron;
@@ -2649,6 +2790,7 @@ Init_Hash(void)
id_default = rb_intern("default");
rb_cHash = rb_define_class("Hash", rb_cObject);
+ rb_cStrHash = rb_define_class("StrHash", rb_cHash);
rb_include_module(rb_cHash, rb_mEnumerable);
@@ -2715,6 +2857,7 @@ Init_Hash(void)
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);
origenviron = environ;
envtbl = rb_obj_alloc(rb_cObject);
diff --git a/include/ruby/intern.h b/include/ruby/intern.h
index cece642..dabe2cd 100644
--- a/include/ruby/intern.h
+++ b/include/ruby/intern.h
@@ -659,6 +659,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 4f607a4..1bb06cf 100644
--- a/string.c
+++ b/string.c
@@ -7012,6 +7012,17 @@ sym_equal(VALUE sym1, VALUE sym2)
return Qfalse;
}
+st_index_t
+rb_sym_hash(VALUE sym){
+ ID id = SYM2ID(sym);
+ return rb_str_hash(rb_id2str(id));
+}
+
+static VALUE
+rb_sym_hash_m(VALUE sym){
+ st_index_t hval = rb_sym_hash(sym);
+ return INT2FIX(hval);
+}
static int
sym_printable(const char *s, const char *send, rb_encoding *enc)
@@ -7495,6 +7506,7 @@ Init_String(void)
rb_define_method(rb_cSymbol, "===", 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..ce43220 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -1,5 +1,5 @@
require 'test/unit'
-require 'continuation'
+#require 'continuation'
class TestHash < Test::Unit::TestCase
@@ -872,3 +872,254 @@ class TestHash < Test::Unit::TestCase
assert_equal({x=>1}.hash, {x=>1}.hash)
end
end
+
+class HashToStrHash < Test::Unit::TestCase
+ def test_strhash
+ hash = { 'a' => 1, 'b' => 2 }
+ assert_instance_of StrHash, hash.strhash
+ assert_equal %w(a b), hash.keys
+ assert_equal [1,2], hash.values
+ end
+end
+
+class TestStrHash < Test::Unit::TestCase
+ def setup
+ @strings = { 'a' => 1, 'b' => 2 }.strhash
+ @symbols = { :a => 1, :b => 2 }.strhash
+ @mixed = { :a => 1, 'b' => 2 }.strhash
+ @fixnums = { 0 => 1, 1 => 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' => 1, 'b' => 2 }.strhash
+ end
+
+ def test_initialize
+ strhash = StrHash.new({ 'a' => 1, 'b' => 2 })
+ assert_equal 1, strhash[:a]
+ strhash = StrHash.new
+ strhash[:a] = 'a'
+ assert_equal 'a', strhash[:a]
+ end
+
+ def test_set
+ array = [{ 'a' => 1, 'b' => 2 }, [:a,:b,:c]]
+ @strings[:array] = array
+ assert_instance_of StrHash, @strings[:array].shift
+ assert_instance_of Array, @strings[:array] = array
+ assert_instance_of StrHash, @strings[:hash] = { 'a' => 1, 'b' => 2 }
+ end
+
+ 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
+ end
+
+ 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
+ end
+
+ 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
+
+ def test_delete
+ @strings.delete('a')
+ assert !@strings.key?(:a)
+ end
+
+ def test_assorted
+ hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
+ method_map = { :'[]' => 1, :fetch => 1, :values_at => [1],
+ :has_key? => true, :include? => true, :key? => true,
+ :member? => 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
+
+ def test_reading
+ hash = StrHash.new
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = 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
+
+ def test_reading_with_nonnil_default
+ hash = StrHash.new(1)
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = 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
+
+ def test_writing
+ hash = StrHash.new
+ hash[:a] = 1
+ hash['b'] = 2
+ hash[3] = 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
+
+ def test_update
+ hash = StrHash.new
+ hash[:a] = 'a'
+ hash['b'] = 'b'
+
+ updated_with_strings = hash.update(@strings)
+ updated_with_symbols = hash.update(@symbols)
+ updated_with_mixed = 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_mixed].all? { |h| h.keys.size == 2 }
+ end
+
+ def test_merging
+ hash = StrHash.new
+ hash[:a] = 'failure'
+ hash['b'] = 'failure'
+
+ other = { 'a' => 1, :b => 2 }
+
+ merged = 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
+
+ def test_deleting
+ get_hash = proc{ StrHash[ :a => 'foo' ] }
+ hash = get_hash.call
+ assert_equal hash.delete(:a), 'foo'
+ assert_equal hash.delete(:a), nil
+ hash = get_hash.call
+ assert_equal hash.delete('a'), 'foo'
+ assert_equal hash.delete('a'), nil
+ end
+
+ 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 = @mixed.dup
+ mixed_with_default.default = '1234'
+ roundtrip = mixed_with_default.strhash.to_hash
+ assert_equal @strings, roundtrip
+ assert_equal '1234', roundtrip.default
+ end
+
+ def test_hash_with_array_of_hashes
+ hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}
+ ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
+ end
+
+ def test_assorted_keys_not_stringified
+ original = {Object.new => 2, 1 => 2, [] => true}
+ indiff = original.strhash
+ assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!")
+ end
+
+ def test_should_use_default_value_for_unknown_key
+ hash_wia = StrHash.new(3)
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_use_default_value_if_no_key_is_supplied
+ hash_wia = StrHash.new(3)
+ assert_equal 3, hash_wia.default
+ end
+
+ def test_should_nil_if_no_default_value_is_supplied
+ hash_wia = StrHash.new
+ assert_nil hash_wia.default
+ end
+
+ def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new(3)
+ hash_wia = hash.strhash
+ assert_equal 3, hash_wia.default
+ end
+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'
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
+
# [ruby-core:3573]
def assert_eval_inspected(sym)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment