追記
Ruby 2.6 からはキーワード引数にシンボル以外のキーを渡すことができなくなるようです, 自然な流れな気がしています.
mrubyのキーワード引数を実装してたらRubyのキーワード引数に謎の挙動を「発見」した。
— Yukihiro Matsumoto (@yukihiro_matz) 2018年7月26日
Non-Symbol key in keyword arguments hash causes an exception.
https://github.com/ruby/ruby/blob/1692ccaf9f49c001b18de8b7296ede3f68190ab8/NEWS より
HashWithIndifferentAccess
Rails (activesupport) で提供されている Hash のサブクラス
特徴として、 Symbol と String を区別せず同一のキーとして取り扱う。
h1 = Hash.new
h1[:foo] = 1
h1[:bar] = 2
h1['bar'] = 3
puts h1[:foo]  # => 1
puts h1['foo'] # => nil
puts h1[:bar]  # => 2
puts h1['bar'] # => 3
h2 = HashWithIndifferentAccess.new
h2[:foo] = 1
h2[:bar] = 2
h2['bar'] = 3
puts h2[:foo]  # => 1
puts h2['foo'] # => 1
puts h2[:bar]  # => 3
puts h2['bar'] # => 3
HashWithIndifferentAccess の内部的な話として、キーに対して Symbol でアクセスが来た際は String に丸めている。
h3 = HashWithIndifferentAccess.new
h3[:foo] = 1
h3['bar'] = 2
h3[:baz] = 3
# Symbol は String に丸められている
h3.keys # => ["foo", "bar", "baz"]
キーワード引数
Ruby 2.0.0 からの機能。メソッドを定義する際、以下のように記述できる。
def test foo: 1, bar: 2, baz: 3
  [foo, bar, baz].join(',')
end
呼び出す際は、以下のような感じ。
以下の、「ハッシュを直接渡すこともできる」の部分が 今回のキモ
# 省略した箇所は、定義中のデフォルトが与えられる
test                           # => '1,2,3'
test foo: 4                    # => '4,2,3'
test foo: 4, bar: 5, baz: 6    # => '4,5,6'
# ハッシュを直接渡すこともできる
arg = {foo: 7, bar: 8, baz: 9}
test arg                       # => '7,8,9'
キーワード引数に HashWithIndifferentAccess を与えてみる
arg = HashWithIndifferentAccess.new({foo: 4, bar: 5, baz: 6})
test arg # => ArgumentError: wrong number of arguments (1 for 0)
- キーワード引数が受け入れ可能なハッシュは、全てのキーが Symbolであることが前提
 参考 ⇒class.cの rb_extract_keywords (と separate_symbol あたり)
- しかしながら、 HashWithIndifferentAccessはキーをStringで保持しているのでキーワード引数の要件を満たさない
ちなみに確認
arg = {foo: 4, bar: 5, "baz" => 6}
test arg # => ArgumentError: wrong number of arguments (1 for 0)
Hash であっても同様で、キーが全て Symbol でなければキーワード引数として受け付けてくれない。
そもそもエラーメッセージの ArgumentError って何言ってんのよ?
追記: 以下の挙動は Ruby 2.5 までのものです.
ArgumentError: wrong number of arguments (1 for 0)
rb_extract_keywordsの説明 より
original_hashで参照されるHashオブジェクトから,Symbolである
キーとその値を新しいHashに取り出します.original_hashの指す
先には,元のHashがSymbol以外のキーを含んでいた場合はそれらが
コピーされた別の新しいHash,そうでなければ0が保存されます.
つまり、キーが全て Symbol でないハッシュを引数に与えると...
arg = {foo: 4, bar: 5, "baz" => 6}
test arg
以下のような呼び出しと扱われる。
test({"baz" => 6}, foo: 4, bar: 5)
(キーワード引数以外の) 引数が 1つ与えられる形になるので
先のエラー (0個引数を与えるべき箇所に1つの引数が渡っている) になる。
# 第一引数を用意しておくと...
def test1 x, foo: 1, bar: 2, baz: 3
  [foo, bar, baz, x].join(',')
end
# 第一引数に arg 全てがわたり、キーワード引数に {} が渡される。
arg = HashWithIndifferentAccess.new({foo: 4, bar: 5, baz: 6})
test1 arg # => "1,2,3,{\"foo\"=>4, \"bar\"=>5, \"baz\"=>6}"