【PHP8.0】throw文がthrow式になる

[throw expression](https://wiki.php.net/rfc/throw_expression)というRFCが投票中です。

最初のアイデアは2019/12/06の[Sebastiaan Andewegによるツイート](https://twitter.com/sebandeweg/status/1202684740383453185)。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">Really liking the arrow functions in PHP 7.4 so far! Unfortunately already found one missing feature: you can&#39;t throw Exceptions from them. The following results in a &#39;unexpected T_THROW&#39; syntax error:<br><br>fn() =&gt; throw new Exception(&#39;nope&#39;);<br><br>Would&#39;ve cleaned up my test, but alas.</p>&mdash; Sebastiaan Andeweg (@sebandeweg) <a href="https://twitter.com/sebandeweg/status/1202684740383453185?ref_src=twsrc%5Etfw">2019年12月5日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


それに対して2020/03/19に[Carusoが反応し](https://twitter.com/carusogabriel/status/1240307196195463172)、そしてその日のうちに[iluuu1994が最初のプルリクを出しました](https://github.com/php/php-src/pull/5279)。
はえーよ。


# throw expression

## Introduction

PHPのthrowは文であるため、アロー関数や三項演算子、NULL合体演算子などの式しか許されない場所から例外を投げることができません。
このRFCでは、それらを可能にするためthrow文を式に変更することを提案します。

## Proposal

式を記述可能なあらゆるコンテキストでthrowが利用可能になります。
以下は思いついた便利そうな例です。

```php
// アロー関数
$callable = fn() => throw new Exception();

// nullチェック
$value = $nullableValue ?? throw new InvalidArgumentException();

// trueっぽいチェック
$value = $falsableValue ?: throw new InvalidArgumentException();

// 空ではない配列チェック
$value = !empty($array)
    ? reset($array)
    : throw new InvalidArgumentException();
```

他にも、議論の余地のある使用方法があります。
このRFCでは、以下のような記述も許可されています。

```php
// ifを使った方が意図が明確になる
$condition && throw new Exception();
$condition || throw new Exception();
$condition and throw new Exception();
$condition or throw new Exception();
```

## Operator precedence

throwが式になると、優先順位を決める必要があります。
以下は現時点で有効な書式の例です。

```php
throw $this->createNotFoundException();
// こうなる
throw ($this->createNotFoundException());
// こうではない
(throw $this)->createNotFoundException();
 
throw static::createNotFoundException();
// こうなる
throw (static::createNotFoundException());
// こうではない
(throw static)::createNotFoundException();
 
throw $userIsAuthorized ? new ForbiddenException() : new UnauthorizedException();
// こうなる
throw ($userIsAuthorized ? new ForbiddenException() : new UnauthorizedException());
// こうではない
(throw $userIsAuthorized) ? new ForbiddenException() : new UnauthorizedException();
 
throw $maybeNullException ?? new Exception();
// こうなる
throw ($maybeNullException ?? new Exception());
// こうではない
(throw $maybeNullException) ?? new Exception();
 
throw $exception = new Exception();
// こうなる
throw ($exception = new Exception());
// こうではない
(throw $exception) = new Exception();
 
throw $exception ??= new Exception();
// こうなる
throw ($exception ??= new Exception());
// こうではない
(throw $exception) ??= new Exception();
 
throw $condition1 && $condition2 ? new Exception1() : new Exception2();
// こうなる
throw ($condition1 && $condition2 ? new Exception1() : new Exception2());
// こうではない
(throw $condition1) && $condition2 ? new Exception1() : new Exception2();
```

共通して言えるのは、全てがthrowキーワードより高い優先順位を持つということです。
このため、このRFCではthrowキーワードの優先順位を可能な限り低くすることを提案します。
現在有効なコードは、たとえ直感に反する動作だったとしても、今後も同じ動作をし続けます。
なぜなら、一般的にthrowは最後に使用するべき演算子であり、それ以降に記述した式は評価されないからです。

低い優先順位の唯一の欠点は、短絡評価のために括弧が必須になることです。

```php
$condition || throw new Exception('$condition must be truthy') && $condition2 || throw new Exception('$condition2 must be truthy');
// こうなる
$condition || (throw new Exception('$condition must be truthy') && $condition2 || (throw new Exception('$condition2 must be truthy')));
// こうではない
$condition || (throw new Exception('$condition must be truthy')) && $condition2 || (throw new Exception('$condition2 must be truthy'));
```

もっとも、こんなコードはほぼ使われていないでしょう。

## Backward Incompatible Changes

後方互換性のない変更はありません。

## Other languages

C#では同じ文法が[2017年に実装](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/throw#the-throw-expression)されました。

このような言語は他にはあまりありません。
ECMAScriptに[プロポーザルがあります](https://github.com/tc39/proposal-throw-expressions)が、これは同じ問題を抱えているからです。

## Proposed PHP Version(s)

PHP8。


# 投票

投票は2020/04/19まで、2/3の賛成で受理されます。
2020/04/06時点では賛成14、反対1で、受理される見込みです。


# 過去のML

[9年前](https://externals.io/message/49569)とか[15年前](https://externals.io/message/15301)にも同じ発想があったようですが、そのときは立ち消えになりました。
当時とはPHPのおかれた環境やRFCの出し方などがだいぶ異なることと、そしてなにより実物のプルリクがあるというのは大きいでしょう。


# 感想

ややこしいよね文と式。
全てが式になればいいのに。

というわけで、今後はもっと気軽にthrowすることができるようになります。
それどころか[アロー関数](https://qiita.com/rana_kualu/items/7fbdd520a7e355599f7d)で引数によって値を返したり例外Aを出したり例外Bを出したりすることもできちゃいますよ。
まあ正直、throwを出すようなややこしい式をアロー関数に書くんじゃないよと思ったりはするわけですが。