diff --git a/hash.c b/hash.c index f34f64065b67d1..23607412d3b6c4 100644 --- a/hash.c +++ b/hash.c @@ -3436,20 +3436,64 @@ rb_hash_to_a(VALUE hash) return ary; } +static bool +symbol_key_needs_quote(VALUE str) +{ + if (!rb_str_symname_p(str)) return true; + const char *s = RSTRING_PTR(str); + char first = s[0]; + if (first == '@' || first == '$' || first == '!') return true; + if (!at_char_boundary(s, s + RSTRING_LEN(str) - 1, RSTRING_END(str), rb_enc_get(str))) return false; + switch (s[RSTRING_LEN(str) - 1]) { + case '+': + case '-': + case '*': + case '/': + case '`': + case '%': + case '^': + case '&': + case '|': + case ']': + case '<': + case '=': + case '>': + case '~': + case '@': + return true; + default: + return false; + } +} + static int inspect_i(VALUE key, VALUE value, VALUE str) { VALUE str2; - str2 = rb_inspect(key); + bool is_symbol = SYMBOL_P(key); + bool quote = false; + if (is_symbol) { + str2 = rb_sym2str(key); + quote = symbol_key_needs_quote(str2); + } + else { + str2 = rb_inspect(key); + } if (RSTRING_LEN(str) > 1) { rb_str_buf_cat_ascii(str, ", "); } else { rb_enc_copy(str, str2); } - rb_str_buf_append(str, str2); - rb_str_buf_cat_ascii(str, "=>"); + if (quote) { + rb_str_buf_append(str, rb_str_inspect(str2)); + } + else { + rb_str_buf_append(str, str2); + } + + rb_str_buf_cat_ascii(str, is_symbol ? ": " : " => "); str2 = rb_inspect(value); rb_str_buf_append(str, str2); diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index f60ba0cffd2743..61b58854e18f25 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -869,6 +869,24 @@ def test_to_s $, = nil end + def test_inspect + no_quote = "{a: 1, a!: 1, a?: 1, \u{3042}: 1}" + quote1 = '{"0": 1, "!": 1, "%": 1, "&": 1, "*": 1, "+": 1, "-": 1, "/": 1, "<": 1, ">": 1, "^": 1, "`": 1, "|": 1, "~": 1}' + quote2 = '{"@a": 1, "$a": 1, "+@": 1, "a=": 1, "[]": 1}' + quote3 = '{"a\"b": 1, "@@a": 1, "<=>": 1, "===": 1, "[]=": 1}' + assert_equal(eval(no_quote), no_quote) + assert_equal(eval(quote1), quote1) + assert_equal(eval(quote2), quote2) + assert_equal(eval(quote3), quote3) + begin + enc, Encoding.default_external = Encoding.default_external, Encoding::Windows_31J + sjis_hash = "{\x87]: 1}".force_encoding('sjis') + assert_equal(eval(sjis_hash).inspect, sjis_hash) + ensure + Encoding.default_external = enc + end + end + def test_update h1 = @cls[ 1 => 2, 2 => 3, 3 => 4 ] h2 = @cls[ 2 => 'two', 4 => 'four' ]