From: "austin (Austin Ziegler) via ruby-core" Date: 2026-07-04T13:10:12+00:00 Subject: [ruby-core:125926] [Ruby Feature#22111] Non-symbolic hash keys with `expr : value` syntax Issue #22111 has been updated by austin (Austin Ziegler). yertto (_ yertto) wrote in #note-35: > However, after talking to a number of Ruby "veterans" about this... > Yes. I concede that the `{ a : 1 }` vs `{ a: 1 }` confusion presents just too much of a mental leap for them to make at this stage. > > So perhaps: > * `{ (expr): value }` _"hash capsule"_ syntax ([reference implementation](https://github.com/ruby/ruby/pull/17299), feature #22108), would be a better choice than, > * `{ expr : value }` _"hash colon"_ syntax ([reference implementation](https://github.com/ruby/ruby/pull/17318), feature #22111) I don't _like_ `{ expr : value }` because I think that it is too confusable and prone to typos, but right now `{ a : 3 }` is a syntax error (at least under prism). ``` irb(main):001> { a : 3 } /Users/austin/.local/share/mise/installs/ruby/4.0.5/lib/ruby/gems/4.0.0/gems/irb-1.16.0/exe/irb:9:in '': (irb):1: syntax errors found (SyntaxError) > 1 | { a : 3 } | ^ expected a `}` to close the hash literal | ^ expected a `=>` between the hash key and value | ^ unexpected ':', ignoring it | ^ unexpected ':', expecting end-of-input | ^ unexpected ':'; expected a value in the hash literal | ^ unexpected '}', ignoring it | ^ unexpected '}', expecting end-of-input ``` So from a parsing perspective, this could _probably_ work. But would `{ a : 3 }` raising `NameError` be better than the syntax error we have now just because one typed `{ a : 3 }` meaning to type `{ a: 3 }`? If `expr` is a function or variable, I think that there's too much chance of the wrong thing being performed when constructing a hash. I don't think that the `key3` ambiguity will _ever_ be acceptable in the examples you provided above, but I think that *if* an `expr` is unambiguous, the parentheses could be skipped. Ambiguous values ��� values which could be bare symbols or string-indicated symbols �����should never be allowed. I think the appropriate stopping point is at column 3. and never at column 4 of your table �����there's little benefit to it over the third step and many downsides, including high levels of confusability resulting in many footguns that will reduce the overall security stance of the Ruby language. ---------------------------------------- Feature #22111: Non-symbolic hash keys with `expr : value` syntax https://bugs.ruby-lang.org/issues/22111#change-117895 * Author: yertto (_ yertto) * Status: Open ---------------------------------------- # Non-symbolic hash keys with `expr : value` syntax Allow `expr : value` for non-symbolic hash keys. Almost 20 years in the making, the missing puzzle piece for Hash's "new" colon syntax: ```ruby h = { name: "symbol shorthand", # Ruby 1.9+ "quoted label": "symbol label", # Ruby 2.2+ (Feature #4935) value_omission: , # Ruby 3.1+ (Feature #14579) expr : "non symbol", # THIS PROPOSAL } ``` ## Motivation Ruby has several colon-based hash key syntaxes for symbolic keys, but the `=>` ("hash rocket") is still needed for non-symbolic keys. Adding `expr : value` is a backwards compatible leap forward toward allowing all-colon hashes for all key types: ```ruby ## Before -- mixed styles n = 42; { key1: "symbol", "key-#{2}": "quoted symbol", RUBY_VERSION:, n => "bar" }.keys # => [:key1, :"key-2", :RUBY_VERSION, 42] ## After -- uniform colon syntax n = 42; { key1: "symbol", "key-#{2}": "quoted symbol", RUBY_VERSION:, n : "bar" }.keys # => [:key1, :"key-2", :RUBY_VERSION, 42] ``` Could this be what we need to one day retire our old friend "hash rocket" from Hashes entirely? ### Completing the colon family Ruby has two hash key syntax families: `=>` (hash rocket) and `:` (hash colon). | Key form | Rocket syntax | Colon syntax | |----------------|-------------------|--------------------| | Static symbol | `:foo => value` | `foo: value` | | Quoted symbol | `:"foo" => value` | `"foo": value` | | Value omission | N/A | `name:` | | Non-symbolic | `expr => value` | ��� **`expr : value` (proposed)** | The gap for non-symbolic keys means they're forced to use rocket syntax. This proposal is to finally complete the hash colon family for all key types. | Example | Ruby | Feature | Key type | |---------|------|---------|----------| | `{ name: value }` | [v1.9](https://docs.ruby-lang.org/en/3.4/NEWS/NEWS-1_9_1.html) | | Symbol | | `{ "quoted label": value }` | [v2.2](https://docs.ruby-lang.org/en/3.4/NEWS/NEWS-2_2_0.html) | [Feature #4935](https://bugs.ruby-lang.org/issues/4935) | Symbol (quoted label) | | `{ value_omission: }` | [v3.1](https://docs.ruby-lang.org/en/3.4/NEWS/NEWS-3_1_0_md.html) | [Feature #14579](https://bugs.ruby-lang.org/issues/14579) | Value omission | | `{ expr : value }` | *???* | [Feature #22111](https://bugs.ruby-lang.org/issues/22111) | Non-symbolic | ### Reducing `=>` overloading The `=>` token is now being used to serve more purposes than just the Hash literal (aka "Hash rocket") it was originally used for. - rightward assignment ``` expr => var ``` - pattern capture ``` case {name: "Alice", role: "admin"} in {name: String => name, role:} # capture the name String value into `name` p name # => "Alice" end ``` - rescue variables ``` rescue SomeExceptionClass => e ``` ``` rescue => e ``` Reducing the rocket's use in Hashes simplifies the language, especially for newcomers. ## Design ### Disambiguation A symbol eligible expression (bareword identifier or quoted string) followed by `:` with **no space** becomes a symbol key. The same expression followed by `:` with **a space** cannot be a label, so it becomes a computed key: ```ruby { a: 1 } # => {:a => 1} symbol { a : 1 } # => {1 => 1} where variable `a=1` (expr-colon where the space disambiguates from a symbol) { "a": 1 } # => {:a => 1} quoted symbol key { "a" : 1 } # => {"a" => 1} string (expr-colon where the space disambiguates from a symbol) ``` Expressions that cannot become symbols will work **with or without a space**, as there is no ambiguity to resolve: ```ruby { 42 : 1 } # => {42 => 1} { 42: 1 } # => {42 => 1} { Math::PI : 1 } # => {3.141592653589793 => 1} { Math::PI: 1 } # => {3.141592653589793 => 1} ``` ## Relationship to Feature #22108 The earlier proposal [Feature #22108](https://bugs.ruby-lang.org/issues/22108) suggested `{ (expr): value }` using parenthesized expressions with a lexer-generated label token (`tLABEL_END`). I assumed there'd be too many dragons to fight with whitespace sensitivity. However, after making the code changes somehow it just worked for all the test cases I threw at it. ��\\_(���)_/�� | | [Feature #22108](https://bugs.ruby-lang.org/issues/22108)
`(expr): value` | [Feature #22111](https://bugs.ruby-lang.org/issues/22111)
`expr : value` | |---|---|---| | Parser changes | Lexer + grammar | Grammar only | | New fields | `hash_nest` | None | | LALR conflicts | 0 | 0 | | Parens required? | Yes | No | | Interpolated key | `("key-#{n}"): val` | `"key-#{n}" : val` | | Integer key | `(42): val` | `42: val` | This version is strictly more general, has a simpler implementation, and requires no lexer changes. ## More examples ```ruby key3 = "key3" def key12 = "key12" key13 = -> { "key13" } h = { key1: 1, "key-2": :two, key3 : "3-expr", "key4" : "4-String", (5+0): "5-parentheses", 6: "6-Integer", 7.001: "7-Float", "key" + "8": "8-String expr", 9+0: "9-Integer expr", [10, 0]: "10-Array", true ? 11 : 0 : "11-ternary", key12(): "12-method", key13[]: "13-lambda[]", -> { "key14" }.call: "14-lambda.call" } p h #=> {key1: 1, "key-2": :two, "key3" => "3-expr", "key4" => "4-String", 5 => "5-parentheses", 6 => "6-Integer", 7.001 => "7-Float", "key8" => "8-String expr", 9 => "9-Integer expr", [10, 0] => "10-Array", 11 => "11-ternary", "key12" => "12-method", "key13" => "13-lambda[]", "key14" => "14-lambda.call"} p h.keys #=> [:key1, :"key-2", "key3", "key4", 5, 6, 7.001, "key8", 9, [10, 0], 11, "key12", "key13", "key14"] ``` ### Familiar to developers from other languages Python dicts have always allowed any hashable key types with `:` syntax. With this proposal something like `{200: "OK", 404: "Not Found"}` can be used in either language. ## Edge cases ```ruby { %"a": 1 } # => {"a" => 1} percent string as computed key { :a: 1 } # => {a: 1} symbol as a symbolic key { :a : 1 } # => {a: 1} symbol as a symbolic key { (:a): 1 } # => {a: 1} symbol as a symbolic key { :"a": 1 } # => {a: 1} quoted symbol as a symbolic key { n : } # syntax error value omission not supported { n : 1, } # => {42 => 1} trailing comma ok ``` ## Implementation Two files (plus tests): - **`parse.y`**: One new production in `assoc`: `| arg_value ':' arg_value` - **`prism/prism.c`**: In `parse_assocs`, accept `PM_TOKEN_COLON` when `pm_symbol_node_label_p` returns false Zero lexer changes, zero new fields, zero LALR conflicts. Reference implementation: [feature/expr-colon-hash-keys](https://github.com/ruby/ruby/pull/17318) PR on GitHub. ## Historical context A version of this was discussed on ruby-core in October 2007 (as part of ["General hash keys for colon notation"](https://public-inbox.org/ruby-core/E1ImQBE-00033s-IZ@x31/T/), murphy). Unfortunately it was brought up during the v1.9 feature freeze, but it looks like [Matz's invitation to discuss for v2.0](https://public-inbox.org/ruby-core/E1ImQA8-00033T-8c@x31/) didn't end up going anywhere. Since then, `{"quoted label": value}` ([Ruby v2.2](https://docs.ruby-lang.org/en/3.4/NEWS/NEWS-2_2_0.html), [Feature #4935](https://bugs.ruby-lang.org/issues/4935)) and `{ value_omission: }` ([Ruby v3.1](https://docs.ruby-lang.org/en/3.4/NEWS/NEWS-3_1_0_md.html), [Feature #14579](https://bugs.ruby-lang.org/issues/14579)) have expanded the colon family, making the computed-key gap more conspicuous. This proposal fills that gap with a [minimal grammar change](https://github.com/ruby/ruby/pull/17318/changes#diff-60e6f0aa6e190c54f6cb0ac74148ced3cf0ef8703e1c3824a07a82dc54654953R6635-R6639) that requires no new lexer states. ## Open questions - Is this syntax acceptable to the community? ## Future directions All colon-based key syntaxes would now be available. This opens up the possibility of eventually deprecating `=>` from Hash literals (while keeping it for rescue, pattern matching, and rightward assignment). This proposal does not require that change ��� it is simply the enabling step, and any deprecation timeline could be a separate discussion. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/