Project

General

Profile

Feature #22111

Updated by yertto (_ yertto) 4 months ago

# 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 

 | Example | Ruby | Feature | Key type | 
 |---------|------|---------|----------| 
 | `{ name: value }` | v1.9 | | Symbol | 
 | `{ "quoted label": value }` | v2.2 | [Feature #4935](https://bugs.ruby-lang.org/issues/4935) | Symbol (quoted label) | 
 | `{ value_omission: }` | v3.1 | [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 label-eligible expression (bareword identifier or quoted string) followed by `:` with **no space** becomes is 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 key,       label syntax 
 { a : 1 }        # => {1 => 1}      variable `a`,     expr-colon (space disambiguates) 
 ``` 

 Expressions that cannot become symbols be labels never need a space, so there is no ambiguity to resolve: 

 ```ruby 
 { "a": 1 }           # => {:a => 1} 
 { "a" : 1 }          # => {"a" => 1} 
 { 42: 1 }            # => {42 => 1} 
 { Math::Pi: 1 }      # => {3.141592653589793 => 1} 
 { foo(): 1 }         # => {"bar" => 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. ¯\\_(ツ)_/¯ 

 | | `(expr): value` | `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" 
 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" 
 } 

 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"} 

 p h.keys 
 #=> [:key1, :"key-2", "key3", "key4", 5, 6, 7.001, "key8", 9, [10, 0], 11] 
 ``` 

 ### 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 
 { 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":` (Ruby 2.2, Feature #4935) and value omission (Ruby 3.1, Feature #14579) have expanded the colon family, making the computed-key gap more conspicuous. 
 This proposal fills that gap with a minimal grammar change that requires no new lexer states. 

 ## Open questions 

 - Is this syntax acceptable to the community? 
 - Should `{ a : }` be a value omission? (Would need compile-time inference.) 

 ## 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. 

Back