サンプルコードでわかる！Ruby 2.7の主な新機能と変更点 Part 2 - キーワード引数に関する仕様変更

## はじめに
Rubyは毎年12月25日にアップデートされます。
Ruby 2.7については2019年11月23日にpreview3がリリースされました。

[Ruby 2\.7\.0\-preview3 リリース](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)

この記事ではRuby 2.7で導入される変更点や新機能について、サンプルコード付きでできるだけわかりやすく紹介していきます。

ただし、Ruby 2.7は多くの新機能や変更点があり、1つの記事に収まらないのでいくつかの記事に分けて書いていきます。
本記事で紹介するのはキーワード引数に関する仕様変更です。

### キーワード引数以外のRuby 2.7の新機能や変更点はこちら

Ruby 2.7ではパターンマッチ以外にもさまざまな新機能や変更点があります。
それらについては以下の記事で紹介しています。こちらもあわせてご覧ください。

- [サンプルコードでわかる！Ruby 2\.7の主な新機能と変更点 Part 1 \- 番号指定パラメータ（numbered parameter） \- Qiita](https://qiita.com/jnchito/items/79f0172e60f237e2c542)
- [サンプルコードでわかる！Ruby 2\.7の新機能・パターンマッチ（前編） \- Qiita](https://qiita.com/jnchito/items/9bb4aa1dcefa00257815)
- [サンプルコードでわかる！Ruby 2\.7の新機能・パターンマッチ（後編） \- Qiita](https://qiita.com/jnchito/items/a9c7030cb5697f992cb7)
- [サンプルコードでわかる！Ruby 2\.7の主な新機能と変更点 Part 3 \- 新機能と変更点の総まとめ \- Qiita](https://qiita.com/jnchito/items/ff89392f46cc9d500c94)

### 本記事の情報源
本記事は以下のような情報源をベースにして、記事を執筆しています。

- [Ruby 2\.7\.0\-preview3 リリース](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)
- [Ruby 3のキーワード引数について考える](https://www.slideshare.net/mametter/ss-114509985)
- [Feature \#14183: "Real" keyword argument \- Ruby master \- Ruby Issue Tracking System](https://bugs.ruby-lang.org/issues/14183)

### 動作確認したRubyのバージョン

本記事は以下の環境で実行した結果を記載しています。

```console
$ ruby -v
ruby 2.7.0preview3 (2019-11-23 master b563439274) [x86_64-darwin19]
```

（2019.12.26追記）正式リリースされたRuby 2.7.0でも動作確認済みです。

```console
$ ruby -v
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19]
```

### フィードバックお待ちしています
本文の説明内容に間違いや不十分な点があった場合は、コメント欄や編集リクエスト等で指摘 or 修正をお願いします🙏

それでは以下が本編です！

## キーワード引数の仕様変更に関する概要

キーワード引数の仕様変更は細かく見ていくとかなりややこしいです。
そこで最初にざっくりと概要を箇条書きにして説明しておきます。

- Ruby 2.0から導入されたキーワード引数は、ハッシュオブジェクトと相互に自動変換できる
- だが、このことが多くの問題を引き起こしている（[参考](https://www.slideshare.net/mametter/ss-114509985)）
- そこで、Ruby 3ではこの自動変換をやめて、普通の引数（特にハッシュオブジェクト）とキーワード引数を完全に分離する
- Ruby 3で後方互換性が失われてしまうため、Ruby 2.7では移行措置としてRuby 3で後方互換性が失われるコードについて、警告を出す

ちなみに、キーワード引数とハッシュオブジェクトの自動変換が行われるのは、こういうコードを書いたときです。（[出典](https://www.slideshare.net/mametter/ss-114509985)）

```ruby
def foo(h)
  p h
end
# キーワード引数からハッシュオブジェクトへの自動変換
foo(k: 42)
#=> {:k=>42}

def foo(k: 1)
  p k
end
# ハッシュオブジェクトからキーワード引数への自動変換
foo({k: 42})
#=> 42
```

上のコードはシンプルなので何も問題がないように見えますが、引数が入り組んでくるとややこしい問題が起きてくるようです。

というわけで、ここから下でキーワード引数に関してどういった仕様変更が入るのかを説明していきます。

なお、この記事では[Ruby 2.7.0-preview3のリリースノート](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)に書かれているそれぞれの説明に対して、僕なりのかみ砕いた説明を入れていくスタイルをとっていきます。

### コラム：オプションを指定して警告の表示を抑制する

Railsアプリケーションのようにさまざまなgemを読み込んでいると、自分では警告対象のコードを書いていなくてもgemのコードが原因で大量の警告が発生する場合があります。

その場合はコマンドラインオプション、または`RUBYOPT`環境変数で以下のようなオプションを指定することで非推奨警告の表示を抑制することができます。

```console 
# コマンドラインオプションで非推奨警告を抑制する
ruby -W:no-deprecated your_code.rb

# RUBYOPT環境変数で非推奨警告を抑制する
RUBYOPT=-W:no-deprecated bundle exec rspec
```

警告の抑制に関する詳しい内容は以下の記事をご覧ください。

[サンプルコードでわかる！Ruby 2\.7の主な新機能と変更点 Part 3 \- 新機能と変更点の総まとめ \- Qiita](https://qiita.com/jnchito/items/ff89392f46cc9d500c94)

## 説明その1：暗黙的にハッシュオブジェクトをキーワード引数に変換している場合

以下は[Ruby 2.7.0-preview3のリリースノート](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)からの引用です。（コードは一部改変）

> メソッド呼び出しにおいて最後の引数としてハッシュオブジェクトを渡し、他にキーワード引数を渡さず、かつ、呼ばれたメソッドがキーワード引数を受け取るとき、警告が表示されます。キーワード引数として扱いたい場合は、明示的にdouble splat演算子（`**`）を足すことで警告を回避できます。このように書けばRuby 3でも同じ意味で動きます。

```ruby
# 警告あり
def foo(key: 0)
  p key
end
foo({key: 42})
#=> The last argument is used as the keyword parameter
#=> 42

# 警告あり
def foo(**kw)
  p kw
end
foo({key: 42})
#=> The last argument is used as the keyword parameter
#=> {:key=>42}

# OK
def foo(key: 0)
  p key
end
foo(**{key: 42})
#=> 42

# OK
def foo(**kw)
  p kw
end
foo(**{key: 42})
#=> {:key=>42}
```

### 警告が出る条件を整理

- メソッド定義側
    - キーワード引数を受け取る
- メソッド呼び出し側
    - 最後の引数としてハッシュオブジェクトを渡す
    - 他にキーワード引数を渡さない

### 解決策

- 引数のハッシュオブジェクトに対して明示的にdouble splat演算子（`**`）を足す

### 詳しい説明

Ruby 2ではハッシュオブジェクトを引数として渡してもキーワード引数に自動変換されていましたが、Ruby 3では、ハッシュオブジェクトとキーワード引数を明確に区別するようになります。

ですので、以下のようなコードはRuby 3ではハッシュオブジェクトそのものを渡していることになります。

```ruby
# fooメソッドにハッシュオブジェクトを渡している
# （Ruby 3ではキーワード引数には自動変換されない）
foo({key: 42})
```

一方、以下のコードはキーワード引数を渡していることになります。

```ruby
# これはキーワード引数を渡している
# （Ruby 3ではハッシュに自動変換されない。
# ただし例外あり。詳しくは後述する説明その4を参照）
foo(key: 42)
```

しかし、突然の仕様変更が入ると既存のコードがRuby 3で動かなくなってしまうため、Ruby 2.7ではハッシュオブジェクトをキーワード引数に自動変換していると警告が出るようになります。

```ruby
# Ruby 2.6では問題なく動いていたが、Ruby 3では動かなくなるため、
# Ruby 2.7では移行措置として警告が出る
foo({key: 42})
#=> The last argument is used as the keyword parameter
```

ハッシュオブジェクトに`**`を付けると、ハッシュオブジェクトをキーワード引数に明示的に変換できます。
こうすれば警告が出なくなり、Ruby 3でも引き続き動かすことができます。

```ruby
# ** を付けるとハッシュオブジェクトをキーワード引数に変換できる
foo(**{key: 42})
```

ちなみに上のコードのように、引数に`**`を付ける構文はRuby 2.6以前でも有効です。（Ruby 2.7で新しく導入された構文ではありません）

## 説明その2：キーワード引数が必須引数として解釈される場合

以下は[Ruby 2.7.0-preview3のリリースノート](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)からの引用です。（コードは一部改変）

> キーワード引数を受け取るメソッドにキーワード引数を渡すが、必須引数が不足している場合に、キーワード引数は最後の必須引数として解釈され、警告が表示されます。警告を回避するには、キーワードではなく明示的にハッシュとして渡してください。このように書けばRuby 3でも同じ意味で動きます。

```ruby
# 警告あり
def foo(h, **kw)
  p(h: h, kw: kw)
end
foo(key: 42)
#=> warning: The keyword argument is passed as the last hash parameter
#=> {:h=>{:key=>42}, :kw=>{}}

# 警告あり
def foo(h, key: 0)
  p(h: h, key: key)
end
foo(key: 42)
#=> warning: The keyword argument is passed as the last hash parameter
#=> {:h=>{:key=>42}, :key=>0}

# OK
def foo(h, **kw)
  p(h: h, kw: kw)
end
foo({key: 42})
#=> {:h=>{:key=>42}, :kw=>{}}

# OK
def foo(h, key: 0)
  p(h: h, key: key)
end
foo({key: 42})
#=> {:h=>{:key=>42}, :key=>0}
```

### 警告が出る条件を整理

- メソッド定義側
    - キーワード引数を受け取る
- メソッド呼び出し側
    - 必須引数（上のコードでいうところの`h`にあたる引数）を渡さず、キーワード引数だけを渡す

### 解決策

- キーワード引数ではなく、ハッシュオブジェクトをメソッドに渡す

### 詳しい説明

Ruby 2では必須引数が不足している状態でキーワード引数を渡すと、キーワード引数ではなく必須引数と見なされていました。

```ruby
def foo(h, key: 0)
  p(h: h, key: key)
end

# キーワード引数のkeyに42を渡すのではなく、必須引数のhに{key: 42}を渡したことになる
foo(key: 42)
#=> {:h=>{:key=>42}, :key=>0}
```

しかし、Ruby 3ではキーワード引数とハッシュオブジェクトを明確に区別するようになるため、先ほどのようなコードは必須引数ではなくキーワード引数を渡していることになります。

```ruby
# Ruby 3ではキーワード引数のkeyに42渡していることになる
# 結果、必須引数のhが不足しているので実行時エラーになる
foo(key: 42)
#=> 何らかのエラー（ArgumentError？）
```

この点もRuby 2とRuby 3で挙動が異なるため、Ruby 2.7では移行措置として警告が出るようになります。

```ruby
# Ruby 2.6では問題なく動いていたが、Ruby 3では動かなくなるため、
# Ruby 2.7では移行措置として警告が出る
foo(key: 42)
#=> warning: The keyword argument is passed as the last hash parameter
```

こういうケースではメソッド呼び出し時にキーワード引数の形式ではなく、明示的にハッシュオブジェクトを渡すようにすれば警告が出なくなり、Ruby 3でもRuby 2と同じように動きます。

```ruby
# キーワード引数ではなく明示的にハッシュオブジェクトを渡せば、
# 必須引数hを渡したことになる
foo({key: 42})
```

ちなみに上のコードのように、明示的にハッシュオブジェクトを渡す呼び出し方はRuby 2.6以前でも有効です。（Ruby 2.6と2.7で挙動の違いはありません）

## 説明その3：ハッシュオブジェクトが普通の引数とキーワード引数に自動分割される場合

以下は[Ruby 2.7.0-preview3のリリースノート](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)からの引用です。（コードは一部改変）

> メソッドがキーワード引数を受け取るがdouble splat引数は受け取らず、かつ、メソッド呼び出しでSymbolと非Symbolの混ざったハッシュを渡す（もしくはハッシュをdouble splatでキーワードとして渡す）場合、ハッシュは分割され、警告が表示されます。Ruby 3でもハッシュの分割を続けたい場合は、呼び出し側で明示的に分けるようにしてください。

```ruby
# 警告あり
def foo(h={}, key: 0)
  p(h: h, key: key)
end
foo("key" => 43, key: 42)
#=> warning: The last argument is split into positional and keyword parameters
#=> {:h=>{"key"=>43}, :key=>42}

# 警告あり
def foo(h={}, key: 0)
  p(h: h, key: key)
end
foo({"key" => 43, key: 42})
#=> warning: The last argument is split into positional and keyword parameters
#=> {:h=>{"key"=>43}, :key=>42}

# OK
def foo(h={}, key: 0)
  p(h: h, key: key)
end
foo({"key" => 43}, key: 42)
#=> {:h=>{"key"=>43}, :key=>42}
```

### 警告が出る条件を整理

- メソッド定義側
    - キーワード引数を受け取る
    - double splat引数（`**`が付いた引数）は受け取らない
- メソッド呼び出し側
    - シンボルと非シンボルの混ざったハッシュを渡す
    - もしくはシンボルと非シンボルの混ざったハッシュをdouble splatでキーワードとして渡す

### 解決策

- メソッド呼び出し側でハッシュはハッシュ、キーワード引数はキーワード引数として明示的に書き分ける

### 詳しい説明

Ruby 2ではシンボルと非シンボルの混ざったハッシュをメソッドに渡すと、普通の引数とキーワード引数を自動的に分割してくれました。

```ruby
# 非シンボルの"key" => 43は普通の引数として、
# シンボルのkey: 42はキーワード引数として、
# それぞれ自動的に分割される
foo("key" => 43, key: 42)

# 同上
foo({"key" => 43, key: 42})
```

しかし、Ruby 3では普通の引数（つまりハッシュオブジェクト）とキーワード引数は明示的に書き分ける必要があります。
ですので、先ほどのコードはRuby 3では以下のように解釈されます。

```ruby
# キーワード引数を2つ渡している
# （が、非シンボルのキーはメソッド定義側で想定していないので実行時エラー）
foo("key" => 43, key: 42)

# 普通の引数（ハッシュオブジェクト）を1つ渡している
# （キーワード引数は指定していないので、デフォルト値のkey: 0が使われる）
foo({"key" => 43, key: 42})
```

この点もRuby 2とRuby 3で引数の解釈が異なるため、Ruby 2.7では移行措置として警告が表示されます。

```ruby
# Ruby 2とRuby 3で引数の解釈が異なるため、Ruby 2.7では移行措置として警告が出る
foo("key" => 43, key: 42)
#=> warning: The last argument is split into positional and keyword parameters

# 同上
foo({"key" => 43, key: 42})
#=> warning: The last argument is split into positional and keyword parameters
```

こういうケースではRuby 3の仕様にあわせて、普通の引数は普通の引数として（つまりハッシュオブジェクトとして）、キーワード引数はキーワード引数としてメソッドを呼び出すように書き換えてください。

```ruby
# 普通の引数（ハッシュ）は{"key" => 43}、キーワード引数はkey: 42と、
# 明確に書き分ければOK
foo({"key" => 43}, key: 42)
```

## 説明その4：擬似的なキーワード引数にキーワード引数を渡す場合（変更なし）

以下は[Ruby 2.7.0-preview3のリリースノート](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)からの引用です。（コードは一部改変）

> メソッドがキーワード引数を受け取らず、呼び出し側でキーワード引数を渡した場合、ハッシュの引数としてみなされる挙動は変わらず、警告も表示されません。Ruby 3でもこのコードは動き続ける予定です。

```ruby
# Ruby 3でも問題なく動く（警告も出ない）
def foo(opt={})
  p opt
end
foo(key: 42)
#=> {:key=>42}
```

### 問題なく動く（＝警告が出ない）条件

- メソッド定義側
    - キーワード引数を受け取らない
- メソッド呼び出し側
    - キーワード引数を渡す

### 詳しい説明

Ruby 3ではメソッド呼び出し時に、普通の引数であれば普通の引数として、キーワード引数であればキーワード引数として書き分けるのが原則になります。
ですが、例外としてメソッド定義側でキーワード引数を受け取らない（＝ハッシュオブジェクトを擬似的なキーワード引数として受け取る）場合は、キーワード引数の記法でメソッドを呼び出すことができます。
この場合はキーワードがハッシュの引数と見なされます。

```ruby
# このメソッドはキーワード引数を受け取らない
# （ハッシュオブジェクトを擬似的なキーワード引数として受け取る）
def foo(opt={})
  p opt
end

# キーワード引数の記法でメソッドを呼び出しているが、ハッシュの引数と見なされる
# （Ruby 2.7やRuby 3でも警告やエラーの対象にならない）
foo(key: 42)

# つまり上のコードは下のコードと同じ意味になる
foo({key: 42})
```

ちなみに、キーワード引数が導入される前（つまりRuby 1時代）に書かれたコードでは、ハッシュを擬似的なキーワード引数として利用していました。
こうしたコードはキーワード引数が導入されたRuby 2以降でもそのまま使われていることが多いです。

たとえば、Rails 6.0の`image_tag`メソッドもメソッド定義は以下のようになっています（[参考](https://github.com/rails/rails/blob/v6.0.2/actionview/lib/action_view/helpers/asset_tag_helper.rb#L340)）。

```ruby
# image_tagメソッドはキーワード引数を受け取らないタイプのメソッド
# （ハッシュオブジェクトを擬似的なキーワード引数として受け取る）
def image_tag(source, options = {})
  # ...
end

# 呼び出し側はキーワード引数のようにoptionsを指定できる
image_tag 'logo.png', class: 'nav-logo'
```

こういうケースではRuby 3でも警告なしに使うことができます。

## 説明その5：非シンボルのキーをキーワード引数として許容するようになった

以下は[Ruby 2.7.0-preview3のリリースノート](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)からの引用です。（コードは一部改変）

> メソッドが任意のキーワードを受け取る場合、非Symbolがキーワード引数のキーとして許容されるようになります。

```ruby
def foo(**kw)
  p kw
end
foo("str" => 1)
#=> {"str"=>1}
```

### 詳しい説明

Ruby 2.6以前では、`**`付きの引数（double splat引数）であってもキーワード引数のキーは必ずシンボルで指定する必要がありました。


```ruby
def foo(**kw)
  p kw
end

# 非シンボルのキーはキーワード引数と見なされない（Ruby 2.6以前）
foo("str" => 1)
#=> ArgumentError (wrong number of arguments (given 1, expected 0))
```

ですが、Ruby 3では非シンボルのキーも許容されるようになります。
Ruby 2.7でもRuby 3と同様に非シンボルのキーを渡すことが可能です。

```ruby
def foo(**kw)
  p kw
end

# Ruby 3やRuby 2.7では非シンボルのキーも渡せる
foo("str" => 1)
#=> {"str"=>1}

# 同上
foo([1, 2, 3] => 1)
#=> {[1, 2, 3]=>1}
```

なお、このようなコードはRuby 2.6以前では動かないコードであったため、既存のコードを修正したりする必要はありません。

## 説明その6：`**nil`でキーワード引数を受け取らないことを明示できるようになった

以下は[Ruby 2.7.0-preview3のリリースノート](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)からの引用です。（コードは一部改変）

> メソッド定義で`**nil`と書くことで、このメソッドがキーワードを受け取らないことを明示できるようになりました。このようなメソッドをキーワード引数付きで呼び出すとArgumentErrorになります。

```ruby
# 実行時エラー
def foo(h, **nil)
  # ...
end
foo(key: 1)
#=> ArgumentError (no keywords accepted)

# 実行時エラー
def foo(h, **nil)
  # ...
end
foo(**{key: 1})
#=> ArgumentError (no keywords accepted)

# 実行時エラー
def foo(h, **nil)
  # ...
end
foo("str" => 1)
#=> ArgumentError (no keywords accepted)

# OK
def foo(h, **nil)
  p(h)
end
foo({key: 1})
#=> {:key=>1}

# OK
def foo(h, **nil)
  p(h)
end
foo({"str" => 1})
#=> {"str"=>1}
```

### 詳しい説明

Ruby 3ではメソッド定義に`**nil`と書くことで、「このメソッドはキーワード引数を一切受け取りません」ということを明示できるようになります。
Ruby 2.7でもこの構文（`**nil`）が使えるようになっています。

```ruby
# Ruby 3やRuby 2.7では**nilでキーワード引数を受け取らないことを明示できる
def foo(h, **nil)
  # ...
end
# キーワード引数を渡そうとすると実行時エラーが起きる
foo(key: 1)
#=> ArgumentError (no keywords accepted)

# 必須引数を一緒に渡す場合も同様
foo({'key' => 1}, key: 1)
#=> ArgumentError (no keywords accepted)
```

`**nil`を指定すると通常のキーワード引数を指定することもできません。（構文エラーが発生します）

```ruby
# **nilと通常のキーワード引数を同時に指定すると構文エラー
def foo(h, key: 0, **nil)
  # ...
end
#=> syntax error, unexpected `nil', expecting ')'
```

なお、Ruby 2.6以前では`**nil`という書き方ができない（構文エラーが発生する）ので、既存のコードを修正したりする必要はありません。

### コラム：擬似的なキーワード引数を受け取るメソッドに`**nil`を追加するとどうなるか

Railsの`image_tag`のようなメソッド（説明その4の「詳しい説明」を参照）に`**nil`を追加するとどうなるでしょうか？
結果は以下のようになります。

```ruby
# Railsのimage_tagメソッド（と同じAPIのメソッド）に**nilを追加してみる
def image_tag(source, options = {}, **nil)
  p("#{source}, options: #{options}")
end

# キーワード引数としてoptionsを渡そうとすると実行時エラーが発生する
image_tag 'logo.png', class: 'nav-logo'
#=> ArgumentError (no keywords accepted)

# optionsはハッシュオブジェクトとして渡せば問題なく呼び出せる
image_tag 'logo.png', {class: 'nav-logo'}
#=> "logo.png, options: {:class=>\"nav-logo\"}"
```

ご覧のとおり、`**nil`を追加するとキーワード引数ではなく、ハッシュオブジェクトとして渡さないと実行時エラーが発生するようになりました。

### コラム：パターンマッチ構文にも登場する`**nil`

`**nil`という記法はRuby 2.7で追加されたパターンマッチ構文にも登場します。

```ruby
case {status: :success, message: ''}
# **nilで「それ以外の要素がないこと」を指定する
in {status: :success, **nil}
  "Success!"
end
#=> NoMatchingPatternError ({:status=>:success, :message=>""})
```

この内容については以下の記事をご覧ください。

- [サンプルコードでわかる！Ruby 2\.7の新機能・パターンマッチ（前編） \- Qiita](https://qiita.com/jnchito/items/9bb4aa1dcefa00257815)

## 説明その7：空のハッシュを`**`付きで渡す場合

以下は[Ruby 2.7.0-preview3のリリースノート](https://www.ruby-lang.org/ja/news/2019/11/23/ruby-2-7-0-preview3-released/)からの引用です。（コードは一部改変）

> キーワード引数を受け取らないメソッドに対して空のハッシュをdouble splatで渡すとき、空のハッシュが渡る挙動はなくなりました。ただし、必須引数が不足する場合は空のハッシュが渡され、警告が表示されます。ハッシュの引数として渡したい場合はdouble splatをつけないようにしてください。

```ruby
h = {}

# OK（ただし、Ruby 2.6以前と挙動が異なるので注意）
def foo(*a)
  p(a)
end
foo(**h)
#=> []

# 警告あり（挙動はRuby 2.6以前と同じ）
def foo(a)
  p(a)
end
foo(**h)
#=> warning: The keyword argument is passed as the last hash parameter
#=> {}

# OK
def foo(*a)
  p(a)
end
foo(h)
#=> [{}]

# OK
def foo(a)
  p(a)
end
foo(h)
#=> {}
```

### 警告が出る条件を整理

- メソッド定義側
    - 必須引数がある
    - キーワード引数を受け取らない
- メソッド呼び出し側
    - 空のハッシュをdouble splat（`**`）で渡す
    - 必須引数が不足している

### 解決策

- ハッシュに`**`を付けずにメソッドを呼び出す

### 詳しい説明

すいません、この話は僕も詳しい理屈（＝正確な言語仕様）がよくわかっていません・・・。
とりあえず、ここでは「わかっている事実」だけを列挙します。

### 空のハッシュを渡すとRuby 2.7から挙動が変わるケース

以下のメソッド呼び出しはRuby 2.6と2.7で挙動が異なります。

```ruby
# 可変長引数は受け取るが、キーワード引数は受け取らないメソッド
def foo(*a)
  p(a)
end

# Ruby 2.6以前では空のハッシュを**付きで渡すと、
# 空のハッシュが可変長引数の要素に含まれる
h = {}
foo(**h)
#=> [{}]

# Ruby 2.7やRuby 3では可変長引数に何も渡されない
# （空の配列になる。また警告も出ない）
foo(**h)
#=> []
```

もし、Ruby 2.7以降で可変長引数の要素として空のハッシュを含めたい場合は（つまりRuby 2.6以前の挙動に合わせたい場合は）、`**`を付けずに呼び出してください。

```ruby
# **を付けずに空のハッシュを渡せば、可変長引数の要素に空のハッシュが含まれる
foo(h)
#=> [{}]
```

ちなみに、上のコードでは空のハッシュを一度変数に入れていますが、メソッド呼び出し時にハッシュリテラルを直接書くと、以下のような挙動になります。

```ruby
def foo(*a)
  p(a)
end

# Ruby 2.6、2.7とも
foo(**{})
#=> []
```

変数に入れた場合と比較すると、Ruby 2.6では実行結果が異なっていることがわかります。（なぜ違いが出るのかは僕もよくわかりません・・・）

```ruby
h = {}

# Ruby 2.6
foo(**h)
#=> [{}]

# Ruby 2.7
foo(**h)
#=> []
```

### 空のハッシュを渡すと警告が出るケース

次に、必須引数はあるがキーワード引数は受け取らないメソッドに対して、空のハッシュをdouble splatで渡すと、Ruby 2.7では警告が出るようになります。

```ruby
# 必須引数はあるが、キーワード引数は受け取らないメソッド
def foo(a)
  p(a)
end

# **付きで空のハッシュを渡すと、必須引数のaに空のハッシュが渡される
# （さらに、Ruby 2.7では警告が出る）
h = {}
foo(**h)
#=> warning: The keyword argument is passed as the last hash parameter
#=> {}
```

こういうケースでは`**`をなくして普通の引数として空のハッシュを渡せば、警告は出なくなり、必須引数の`a`にも空のハッシュが格納されます。

```ruby
foo(h)
#=> {}
```

ちなみに、この場合も直接ハッシュリテラルを書くと、Ruby 2.6以前では挙動が異なります。（理由は未調査）

```ruby
def foo(a)
  p(a)
end

# Ruby 2.6ではエラー
foo(**{})
#=> ArgumentError (wrong number of arguments (given 0, expected 1))

# Ruby 2.7では警告付きで実行可能
foo(**{})
#=> warning: The keyword argument is passed as the last hash parameter
#=> {}
```

先ほども述べたとおり、空のハッシュに関する話は僕もなぜこうなるのか、詳しい理由がよくわかっていません。
ただ、ふだん素直にコードを書いていれば、わざわざ空のハッシュに`**`を付けてメソッドを呼び出すようなことは、まずないと思います。（少なくとも僕はそんなコードを書いた記憶がありません）
ですので、大半の人にとってはこの話を完璧に理解する必要はなく、頭の片隅に置いておく程度でも大丈夫なのではないでしょうか。

詳しく知りたい方は以下のissueの議論を追いかけていくと、理由やヒントが見つかるかもしれません。（すいません、僕は付いていけなくて挫折しました😣）

[Feature \#14183: "Real" keyword argument \- Ruby master \- Ruby Issue Tracking System](https://bugs.ruby-lang.org/issues/14183)

### 参考：空ではないハッシュを渡すとどうなるのか

上では空のハッシュを渡すときの挙動が問題になっていましたが、空ではないハッシュを渡したときはどうなるのでしょうか？
試しにやってみたので、実行結果を以下に載せておきます。

なお、いずれもRuby 2.6と2.7では挙動が変わらず、警告も発生しませんでした。

```ruby
h = {a: 1}

def foo(*a)
  p(a)
end
foo(**h)
#=> [{:a=>1}]
foo(**{a: 1})
#=> [{:a=>1}]

def foo(a)
  p(a)
end
foo(**h)
#=> {:a=>1}
foo(**{a: 1})
#=> {:a=>1}

def foo(*a)
  p(a)
end
foo(h)
#=> [{:a=>1}]

def foo(a)
  p(a)
end
foo(h)
#=> {:a=>1}
```

## まとめ

というわけで、この記事ではRuby 2.7から導入されるキーワード引数に関する仕様変更点を説明しました。
すべてのポイントを完全に理解するのはかなり大変だと思います。

ただ、僕の経験に限って言えば、「もしかしたら自分が書いたコードで修正が必要になるかも？」と思ったのは、説明その1のケースぐらいでした。
それ以外は「わざわざこんなコードを書いたことはないなあ」と思うものばかりです。

ですので、完全に理解しなくても日常的にコードを読み書きする上ではあまり困らないかもしれません。

とはいえ、gemのコードにこうした含まれていて、Ruby 2.7に上げた途端、警告が大量に発生する、ということは十分考えられます。
その場合はこの記事を参考にしながら、落ち着いて対処するようにしてください。
（修正のPull requestを作ってあげるのがベストですね😉）

**P.S.**
それにしてもこんなややこしい問題と日々戦っておられるRubyコミッタのみなさんには、ほんとうに頭が上がりませんね・・・。どうもありがとうございます！

### あわせて読みたい

Rubyコミッタの遠藤さんが以下の記事で、キーワード引数に関する仕様変更について説明されています。こちらもぜひご覧ください。

[プロと読み解くRuby 2\.7 NEWS \- クックパッド開発者ブログ](https://techlife.cookpad.com/entry/2019/12/25/121834)

また、既存のコードを修正する場合のより複雑なケースについては、公式の移行ガイド（英語）を参照してください。

[Separation of positional and keyword arguments in Ruby 3\.0](https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/)