TypeScript 2.0のneverでTagged union typesの絞込を漏れ無くチェックする

# はじめに

TypeScript 2.0で追加される[Tagged (or discriminated) union types](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#tagged-union-types)により、[String literal types](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#string-literal-types)のプロパティでif文やswitch文でObjectの型を絞り込むことができるが、同じく2.0で追加される[The never type](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#the-never-type)と合わせて使うと、絞り込み対象の[Union Types](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#union-types)を網羅的にif文やswitch文で処理しているかどうかをコンパイラでチェックすることができる。

# 例
先日投稿した[Redux typed actions でReducerを型安全に書く (TypeScriptのバージョン別)](http://qiita.com/wadahiro/items/7c421b668f28a99e2a29)で作成した[サンプルアプリ](https://github.com/wadahiro/react-redux-typescript-boilerplate)のRedux Reducerを例に説明する。

このReducerは、[Union Types](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#union-types)である`Actions.Actions`を、`switch case`にて`type`プロパティで判別して処理している。
その際、`switch case`にて`Actions.Actions`のすべての`type`に対して漏れなく処理していることをコンパイラレベルで検出しよう、というのが今回の話。

```ts
// Union Types
export type Actions = IncrementAction | DecrementAction | SetCounterAction;

export interface IncrementAction extends Action {
    type: 'INCREMENT';
}
export function increment(): IncrementAction {
    return {
        type: 'INCREMENT'
    };
}

export interface DecrementAction extends Action {
    type: 'DECREMENT';
}
export function decrement(): DecrementAction {
    return {
        type: 'DECREMENT'
    };
}

export interface SetCounterAction extends Action {
    type: 'SET_COUNT';
    payload: {
        count: number;
    }
}
export function setCount(num: number): SetCounterAction {
    return {
        type: 'SET_COUNT',
        payload: {
            count: num
        }
    };
}

// Reducer
export const appStateReducer = (state: AppState = init(), action: Actions.Actions) => {
    switch (action.type) {

        case 'INCREMENT':
            return Object.assign({}, state, {
                count: state.count + 1
            });

        case 'DECREMENT':
            return Object.assign({}, state, {
                count: state.count + -1
            });

        case 'SET_COUNT':
            return Object.assign({}, state, {
                count: action.payload.count
            });
    }

    return state;
};
```

## never型を使わない場合

`case 'SET_COUNT':`の時の処理をまるっと消してみる。もちろん、何もチェックは働かずコンパイラはエラーを検出することはできない。

![no-never.png](https://qiita-image-store.s3.amazonaws.com/0/35233/43b48474-821a-7efb-6e12-d862665c4587.png)


## never型を使った場合
次にnever型を使ってみた場合。使い方は、下記のように`never`型の変数に`action`変数をアサインする処理を追加する。

```ts
// neverでチェックするようにしたReducer
export const appStateReducer = (state: AppState = init(), action: Actions.Actions) => {
    switch (action.type) {

        case 'INCREMENT':
            return Object.assign({}, state, {
                count: state.count + 1
            });

        case 'DECREMENT':
            return Object.assign({}, state, {
                count: state.count + -1
            });

        case 'SET_COUNT':
            return Object.assign({}, state, {
                count: action.payload.count
            });

        // never型でチェック
        default: 
            const _exhaustiveCheck: never = action;
    }

    return state;
};
```

これで先ほどと同じく`case 'SET_COUNT':`の処理を消すと...

![use-never.png](https://qiita-image-store.s3.amazonaws.com/0/35233/dd88b95c-9266-dbcb-ff57-6f2f1f7efe92.png)

このように、`never`型にアサイン不可のためコンパイラによりエラーが検出される。
`Actions.Actions`のすべての`type`プロパティに対して処理をしている場合はここは到達不可能となり、`action`は`never`型になる。よって`never`型の変数へのアサインではエラーにならなくなるというわけだ。


## if文でnever型を使った場合

念のためif文でも動作確認してみたところ、TypeScript 2.0では不足時のチェックは働くが、すべての条件を網羅的に書いた時もコンパイラエラーが出てしまうようで駄目でした。

![if-never.png](https://qiita-image-store.s3.amazonaws.com/0/35233/8c7774ae-de51-a7ef-558e-0300d2c898b6.png)

一方、`npm i typescript@next`でインストール可能な開発中のTypeScript 2.1だと上記の誤検知エラーはなくなり大丈夫でした。

# まとめ
というわけではやく2.1がリリースされて欲しい...!!

# 参考
今回の`never`型を使ったチェックは[TypeScript Deep Dive](https://github.com/basarat/typescript-book)で[紹介されていました](https://github.com/basarat/typescript-book/blob/master/docs/types/discriminated-unions.md)。