From: "yertto (_ yertto) via ruby-core" Date: 2026-07-04T07:40:36+00:00 Subject: [ruby-core:125925] [Ruby Feature#22111] Non-symbolic hash keys with `expr : value` syntax Issue #22111 has been updated by yertto (_ yertto). Thanks @baweaver. (This proposal is not _just_ to free up String keys, but - yes - there must be a considerable amount of code and authors that have been, and will continue to be, affected just by this lack of support for String keys in pattern matching.) > I'll be direct: disambiguating semantics based on whitespace around a colon is going to be a source of bugs and confusion. One could argue that a similar whitespace problem _already_ occurs in the language when trying to figure out why `a` is a variable and `:a` is a symbol. 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) The thing is though, that the _hash colon_ implementation (which essentially just allows a `:` to be used as an alternative to `=>`) is a lot simpler, a lot less code AND supports a superset of key syntaxes - including the use of _hash capsules_ - out of the box. So my thinking is that while the Ruby language could support _hash colon_ syntax in full, the feature and all its documentation could be released in a way that only mentions the introduction of the _hash capsule_ syntax `(...):` as an alternative to the _hash rocket_ `=>` for non-symbolic keys. Then, when the community (and rubocop) see _hash capsules'_ redundancy, parentheses could slowly fade and _hash colon_ syntax can "just work" (as it always did). (ie. similar to how many users may have initially used parentheses in their calls to `some_method()`, but eventually felt comfortable enough just calling `some_method` instead.) > The colon family has been expanding for almost 20 years: > ... > Each one was met with resistance when added, but each grew on the Ruby community over time. I believe it's time to consider completing this arc for non-Symbol keys, but I want to make sure to do it in a way that does not give distinct meaning to whitespace that could cause errors and confusion. What would you think of the _hash colon_ syntax being slowly phased in like so: ### Hash Colon Syntax phase in... | Current Syntax | Hash Colon
(using Hash Capsules on
_all_ non-symbolic keys) | Hash Colon
_(using Hash Capsules only
where necessary to avoid
whitespace ambiguity)_ | Hash Colon
_(with ?acceptable?
whitespace ambiguity)_ | |----------------|--------------|----------------------------------------|------------------------------------------| | `{ key1: 1 }` | `{ key1: 1 }` | `{ key1: 1 }` | `{ key1: 1 }` | | `{ "key-2": 2 }` | `{ "key-2": 2 }` | `{ "key-2": 2 }` | `{ "key-2": 2 }` | | `{ key3 => 3 }` | `{ (key3): 3 }` | _`{ (key3): 3 }`_ | _`{ key3 : 3 }`_ | | `{ "key4" => 4 }` | `{ ("key4"): 4 }` | _`{ ("key4"): 4 }`_ | _`{ "key4" : 4 }`_ | | `{ 5+0 => 5 }` | `{ (5+0): 5 }` | `{ 5+0: 5 }` | `{ 5+0: 5 }` | | `{ 6 => 6 }` | `{ (6): 6 }` | `{ 6: 6 }` | `{ 6: 6 }` | | `{ 7.001 => 7 }` | `{ (7.001): 7 }` | `{ 7.001: 7 }` | `{ 7.001: 7 }` | | `{ "key" + "8" => 8 }` | `{ ("key" + "8"): 8 }` | `{ "key" + "8": 8 }` | `{ "key" + "8": 8 }` | | `{ 9+0 => 9 }` | `{ (9+0): 9 }` | `{ 9+0: 9 }` | `{ 9+0: 9 }` | | `{ [10, 0] => 10 }` | `{ ([10, 0]): 10 }` | `{ [10, 0]: 10 }` | `{ [10, 0]: 10 }` | Where it could be left to the community to one day decide if that final phase (with its whitespace ambiguity) is acceptable or not. Is that a workable compromise @nobu / @shyouhei ? ---------------------------------------- Feature #22111: Non-symbolic hash keys with `expr : value` syntax https://bugs.ruby-lang.org/issues/22111#change-117894 * 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/