【PHP8.1】呼び出し元に返らない返り値の型が指定できるようになる

```php
function foo():XXX{
	exit;
}
```

この関数の返り値の型は何にすればいいでしょうか。
null？
void？

nullはnullという型ですし、voidは『値を返さない』であって『呼び出し元に返らない』ではありません。
ということで『呼び出し元に返らない』を明記できる型が提案されました。
返らないのに返り値とは。

PHP8.1以降ではこう書けるようになります。

```php
function foo():never{
	exit;
}
```

以下は該当のRFC、[PHP RFC: noreturn type](https://wiki.php.net/rfc/noreturn_type)の日本語訳です。


# PHP RFC: noreturn type

## Introduction

ここ数年の傾向として、元々はPHP docで表現されていた型がPHPネイティブになっていくということがあります。
過去の例としては[スカラー型](https://wiki.php.net/rfc/scalar_type_hints_v5)、[返り値の型](https://wiki.php.net/rfc/return_types)、[UNION型](https://wiki.php.net/rfc/union_types_v2)、[mixed型](https://wiki.php.net/rfc/mixed_type_v2)、[static型](https://wiki.php.net/rfc/static_return_type)などです。

現在、PHPの静的解析ツールは、常に例外を発したり常に`exit`したりする関数を示すために、`@return noreturn`構文をサポートしています。
ツールのユーザは、自分のコードの動作を表すためにこの構文が便利だと感じていると思いますが、PHPネイティブのコンパイル・実行時の型チェックがサポートすればより便利になると思います。

## Proposal

戻り値の型として`noreturn`を導入する。

リダイレクトする関数は、この戻り値型のよいサンプルです。

```php
function redirect(string $uri): noreturn {
    header('Location: ' . $uri);
    exit();
}

function redirectToLoginPage(): noreturn {
    redirect('/login');
}
```

PHP開発者は、この関数を呼び出したときに、その後の文が評価されないことが保証されるという安心感を得ることができます。

```php
function sayHello(?User $user) {
    if (!$user) {
        redirectToLoginPage();
    }

    echo 'Hello ' . $user->getName(); // redirectToLoginPageが呼ばれたらここには絶対に来ない
}
```

後から`redirect`関数に`return`を追加しようとしても、コンパイルエラーが発生します。

```php
function redirect(string $uri): noreturn {
    if ($uri === '') {
        return; // Fatal error: A noreturn function must not return
    }
    header('Location: ' . $uri);
    exit();
}
```

暗黙的な`return`にしようとした場合は、TypeErrorが発生します。

```php
function redirect(string $uri): noreturn {
    if ($uri !== '') {
        header('Location: ' . $uri);
        exit();
    }
}

redirect(''); // Uncaught TypeError: redirect(): Nothing was expected to be returned
```

`noreturn`関数内で`yield`を使うとコンパイルエラーになります。

```php
function generateList(string $uri): noreturn {
    yield 1;
    exit();
}
// Fatal error: Generator return type must be a supertype of Generator
```

### Applicability

`noreturn`型は、`void`型と同じく返り値の型としてのみ有効です。
引数やプロパティとして使おうとするとコンパイルエラーになります。

```php
class A {
    public noreturn $x; // Fatal error
}
```

### Variance

型理論において、`noreturn`型は[ボトム型](https://ja.wikipedia.org/wiki/%E3%83%9C%E3%83%88%E3%83%A0%E5%9E%8B)とされます。
すなわち、PHPの型システムにおいては、`noreturn`型は`void`を含む全ての型のサブタイプになるということです。
従って、`noreturn`型は他のサブタイプと同じルールに従います。

返り値の型を狭めることができます。

```php
abstract class Person
{
    abstract public function hasAgreedToTerms(): bool;
}

class Kid extends Person
{
    public function hasAgreedToTerms(): noreturn
    {
        throw new \Exception('Kids cannot legally agree to terms');
    }
}
```

返り値の型を広げることはできません。

```php
abstract class Redirector
{
    abstract public function execute(): noreturn;
}

class BadRedirector extends Redirector
{
    public function execute(): void {} // Fatal error
}
```

リファレンスを使うことも可能です。

```php
class A {
    public function &test(): int { ... }
}
class B extends A {
    public function &test(): noreturn { throw new Exception; }
}
```

`__toString`メソッドにも適用可能です。

```php
class A implements Stringable {
    public function __toString(): string {
        return "hello";
    }
}

class B extends A {
    public function __toString(): noreturn {
        throw new \Exception('not supported');
    }
}
```

`noreturn`型は全ての型のサブタイプであるため、他の型で問題なくアノテーションできます。

```php
function doFoo(): int
{
    throw new \Exception();
}
```

### Prior art in other interpreted languages

他のインタプリタ型での同様な実装。

- Hacklang [noreturn型](https://docs.hhvm.com/hack/built-in-types/noreturn)
- TypeScript [never型](https://www.typescriptlang.org/docs/handbook/basic-types.html#never)
- Python [NoReturn型](https://docs.python.org/3/library/typing.html#typing.NoReturn)


### Prior art in PHP static analysis tools

PHP静的解析ツールでの同様な実装。

- PsalmとPHPStanは、`/** @return noreturn */`をサポートしている。
- PHPStormは、PHP8のアトリビュートで`#[JetBrains\PhpStorm\NoReturn]`をサポートしている。

### Comparison to void

`noreturn`と`void`は、いずれも返り値の型にしか書けないということは同じですが、類似点はそれだけです。

`void`型の関数を呼んだ場合、通常はそれ以降のプログラムも実行されるという想定です。

```php
function sayHello(string $name): void {
    echo "Hello $name";
}
 
sayHello('World');
echo ", it’s nice to meet you";
```

`noreturn`型の関数は、その後の文が実行されることはありません。

```php
function redirect(string $uri): noreturn {
    header('Location: ' . $uri);
    exit();
}
 
redirect('/index.html');
echo "this will never be executed!";
```

### Naming

ネーミングは難しい。

- noreturn

・既存のクラス名として使われている可能性は低い。
・関数っぽい名前。

- never

・1単語であり、`no-return`みたいに区切りを入れたくなる衝動がおこらない。
・特定状況で使われるキーワードではなく、本格的な型として扱える。遠い将来[ジェネリクス](https://docs.hhvm.com/hack/built-in-types/nothing#usages)が入ったときにも使える単語だろう。


## Backwards Incompatible Changes

互換性のない変更として、`never`が予約語に追加されます。

## Proposed PHP Version(s)

PHP 8.1。

## Patches and Tests

[Support noreturn types #6761](https://github.com/php/php-src/pull/6761)

## Vote

投票は2021/03/30から2021/04/13に行われ、賛成42反対11で受理されました。

またキーワードについて、`noreturn`・`never`どちらがよいかという投票も同時に行われました。
こちらは`noreturn`14票・`never`34票となっており、`never`に決定しました。

RFCの本文は`noreturn`前提で書かれているのですが、まあそのうち書き替えられると思います。


# 感想

never型は、[void型](https://qiita.com/tadsan/items/154070f1ee4c7fe4ae47#php71%E3%81%AE%E5%9E%8B%E5%AE%A3%E8%A8%80%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8Bvoid)や[false疑似型](https://qiita.com/rana_kualu/items/f75e254553157d8eb0b7#false%E7%96%91%E4%BC%BC%E5%9E%8B)同様、返り値にしか書けない特殊な型となります。

途中でexitするなんて他の言語ではなかなか考えづらいものがありますが、PHPの場合はリダイレクトという非常に自然な例が存在します。

```php
function redirect(string $url): never {
	header('Location: ' . $url);
	exit;
}
```

これについては、他の書き方の方がかえって不自然になるでしょう。
こういうところに使えば、サジェストなどに出てくるため使い勝手がよりよくなりますね。

もっともフレームワークを使っていれば、あまり目にする機会はなさそうです。
たとえばLaravelだとユーザコード上で`exit`することは考えられておらず、[リダイレクト](https://readouble.com/laravel/8.x/ja/redirects)も`RedirectResponse`を適当に設定して`return`で送り返せ、みたいな設計だったりするので、ユーザから見える範囲にnever型が現れてくることはないでしょう。

……いや待て[dd](https://readouble.com/laravel/8.x/ja/helpers.html#method-dd)があった！
これで`dd`と`dump`の動作の違いをシグネチャで区別できるようになるぞ。
何の意味があるのかはわかりません。