Project

General

Profile

Actions

Feature #22111

open

Non-symbolic hash keys with `expr : value` syntax

Feature #22111: Non-symbolic hash keys with `expr : value` syntax

Added by yertto (_ yertto) about 18 hours ago. Updated about 10 hours ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:125754]

Description

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:

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:

## 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 Symbol
{ "quoted label": value } v2.2 Feature #4935 Symbol (quoted label)
{ value_omission: } v3.1 Feature #14579 Value omission
{ expr : value } ??? Feature #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:

{ a: 1 }    # => {:a => 1}   symbol
{ a : 1 }   # => {1 => 1}    variable `a`      (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:


{ 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 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
(expr): value
Feature #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

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

{ %"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 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", murphy).
Unfortunately it was brought up during the v1.9 feature freeze, but it looks like Matz's invitation to discuss for v2.0 didn't end up going anywhere.
Since then, {"quoted label": value} (Ruby v2.2, Feature #4935) and { value_omission: } (Ruby v3.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?

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.

Updated by yertto (_ yertto) about 18 hours ago Actions #1

  • Description updated (diff)

Updated by yertto (_ yertto) about 17 hours ago Actions #2

  • Tracker changed from Bug to Feature
  • Backport deleted (3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN)

Updated by yertto (_ yertto) about 17 hours ago Actions #3

  • Description updated (diff)

Updated by yertto (_ yertto) about 16 hours ago Actions #4

  • Description updated (diff)

Updated by yertto (_ yertto) about 16 hours ago Actions #5

  • Description updated (diff)

Updated by yertto (_ yertto) about 16 hours ago Actions #6

  • Description updated (diff)

Updated by yertto (_ yertto) about 16 hours ago Actions #7

  • Description updated (diff)

Updated by yertto (_ yertto) about 16 hours ago Actions #8

  • Description updated (diff)

Updated by yertto (_ yertto) about 16 hours ago Actions #9

  • Description updated (diff)

Updated by yertto (_ yertto) about 16 hours ago Actions #10

  • Description updated (diff)

Updated by yertto (_ yertto) about 16 hours ago Actions #11

  • Description updated (diff)

Updated by yertto (_ yertto) about 15 hours ago Actions #12

  • Description updated (diff)

Updated by yertto (_ yertto) about 14 hours ago Actions #13

  • Description updated (diff)

Updated by yertto (_ yertto) about 14 hours ago Actions #14

  • Description updated (diff)

Updated by yertto (_ yertto) about 14 hours ago Actions #15

  • Description updated (diff)

Updated by yertto (_ yertto) about 14 hours ago Actions #16

  • Description updated (diff)

Updated by yertto (_ yertto) about 11 hours ago Actions #17

  • Description updated (diff)

Updated by yertto (_ yertto) about 11 hours ago Actions #18

  • Description updated (diff)

Updated by yertto (_ yertto) about 11 hours ago Actions #19

  • Description updated (diff)

Updated by yertto (_ yertto) about 10 hours ago Actions #20

  • Description updated (diff)

Updated by yertto (_ yertto) about 10 hours ago Actions #21

  • Description updated (diff)

Updated by yertto (_ yertto) about 10 hours ago Actions #22

  • Description updated (diff)

Updated by yertto (_ yertto) about 10 hours ago Actions #23

  • Description updated (diff)

Updated by yertto (_ yertto) about 10 hours ago Actions #24

  • Description updated (diff)

Updated by yertto (_ yertto) about 10 hours ago Actions #25

  • Description updated (diff)

Updated by yertto (_ yertto) about 10 hours ago Actions #26

  • Description updated (diff)

Updated by yertto (_ yertto) about 10 hours ago Actions #27

  • Description updated (diff)
Actions

Also available in: PDF Atom