TypeScriptで初期値を指定して変数を設定すると==演算子でエラーになる時がある


TypeScriptが以下の挙動をする理由を教えてください。  
TypeScriptのバージョンは2.2.1、Visual Studio Codeを使用しています。

[code]

    var hoge:boolean = true;
    var fuga = hoge==false ? 123 : 456;  
    
[/code]

このコードだと`hoge==false`の部分がエラー

> error TS2365: Operator '==' cannot be applied to types 'true' and  
>  'false'.

になりますが、

[code]

    var hoge:boolean;
    hoge = true;
    var fuga = hoge==false ? 123 : 456;  
    
[/code]

このコードのように変数の宣言と代入を別のところで行うと`hoge==false`の部分がエラーに  
なりません。  
この違いはどうして起こるのでしょうか？




投稿の内容に誤りと思われる箇所がありましたので訂正します。また、[mjyさんによる回答](https://ja.stackoverflow.com/questions/37793/#37825)を推薦します。以下参考までにご覧いただければ幸いです。

* * *

[basarat/typescript-book](https://github.com/basarat/typescript-
book)の[TypeScript's Type System > Literal
Types](https://basarat.gitbooks.io/typescript/docs/types/literal-
types.html)が参考になるかと思われます。

TypeScriptにおける _Literal Types_ とは、雑に言えば「リテラルとして存在する値 **固有**
の型」のようなものです。たとえば、`true`という **リテラル** は _literal true型_
の値`true`です。あるいは、`false`という **リテラル** は _literal false型_ の値`false`です。

[code]

    true === (true as true);
    false === (false as false);
    
[/code]

literal true/false型は明らかにprimitive
Booleanの派生型ですが、両者の比較に際してはそれぞれがtrueまたはfalseしかとらない固有の型であるために、異なる型を比較したような状態となり型エラーとなります。

このような比較の例として、特にBoolean,Number,String型のリテラルどうしの比較は（意味があるかはともかくとして）同様の理由によりTypeScriptでは許容されません。

[code]

    true === false;  // (true as true) === (false as false);
    1 === 2;         // (1 as 1) === (2 as 2);
    'foo' === 'bar'; // ('foo' as foo) === ('bar' as bar);
    // incompatible types
    
[/code]

では質問のコードですが、

[code]

    hoge==false ? ...
    
[/code]

において、比較演算子`==`の両辺の値がどのような型として扱われるかが問題です。  
右辺`false`は先述のとおりリテラル固有のliteral false型として解釈されます。  
一方左辺`hoge`は定義式に基づくとprimitive Boolean型です。もしこのとおりに解釈されたのであれば、派生型であるliteral
false型との比較は可能であり、エラーはでません。

[code]

    // もしhogeがboolean型であるとされた場合
    hoge==false
    (hoge as boolean) == ((false as false) as boolean)
    //       派生型のためキャスト（比較）可  ^^^^^^^^^
    
[/code]

一方で、質問のいずれのコードについても、処理を追えば比較部分に到達する時点で変数`hoge`の値は100%の確率で、つまり静的に`true`であることがわかります。これを考慮すればhogeの型も静的に決定可能であり、具体的にはliteral
true型です。TypeScriptの型システムにおいてこうした解釈がなされるとすれば、言及されているような型エラーが出ることは理解されます。

[code]

    var hoge:boolean;
    hoge = true;
    // ここに到達する時点で変数hogeがliteral true型であることは
    // 静的に（コードを実行しなくても）解析可能であるので、
    // boolean型と定義されているものの（より厳密に）literal true型として扱われる
    hoge==false ? ...
    // (hoge as true) === (false as false)
    //       ^^^^^^^ 比較不能
    
[/code]

こうした型を決定する静的解析システムは、TypeScriptにおいては[CFA](https://github.com/Microsoft/TypeScript/wiki/What's-new-
in-TypeScript#control-flow-based-type-analysis)とよばれています。

反対の例を挙げれば理解が深まるかもしれません。たとえばCFAが「実質的に」primitive Boolean型であるとみなしliteral
true/false型として扱わないケースは次のコードです:

[code]

    let hoge: boolean;
    // 次のコードは結果を静的に解析できない
    // => 「実質的に」boolean型として扱われる
    hoge = (Math.random() > 0.5);
    // 変数hogeはboolean型として解釈され、エラーは出ない
    hoge === false ? ...
    
[/code]

こうした取り組み、すなわち「より厳密に」「より実質的に」型をチェックすることで、native
jsでは膨大な検証コードを関数の冒頭に必要としたような状況をTypeScriptは解消しています。

上に上げたようなふるまいの是非など、この手の話題は[issue](https://github.com/Microsoft/TypeScript/issues)を覗くとよく議論されている（はず）ですので、興味があればご確認ください。

* * *

この存在価値がいまいちな _literal types_ ですが、よりプリミティブな実装の例としては _typeof operator_
の返り値の型がそうです。次のコードはこの質問のエラーとまったく同様のエラーになります:

[code]

    typeof hoge === 'nantokakanntoka'
    // Operator '===' cannot be applied to types '"string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"' and '"nantokakanntoka"'.
    
[/code]

これは、typeofの返り値が内部で _literal "string" | "number" | "boolean" | "symbol" |
"undefined" | "object" | "function"型_
と定義されているからです。便宜上typeofを関数のように書けば、次のような疑似コードで定義されているものと考えてください。

[code]

    function typeof(obj: any): "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" {
        // ...
    }
    
[/code]

この定義のありがたさとしては、native jsがtypeofの返り値をprimitive
String型としているためにまったく不正な型名`nantokakanntoka`との比較を許容してしまうことに対して、typescriptではliteral
typesを用いてenumのような型付けを簡単に実装できてしまい、かつ型名をいちいち名前をつけて定義していないところです。  
このようにliteral typesは「もうちょっとその型のなかでも値を制限したいけど型名をつくるのは大げさだしreturn
typeに書きたいし、」みたいなところで活躍するものと（主観では）思います。

以上、個人の理解に基づく内容ですので、誤り等あればご指摘ください。




投稿の内容に誤りと思われる箇所がありましたので訂正します。また、[mjyさんによる回答](https://ja.stackoverflow.com/questions/37793/#37825)を推薦します。以下参考までにご覧いただければ幸いです。

* * *

[basarat/typescript-book](https://github.com/basarat/typescript-
book)の[TypeScript's Type System > Literal
Types](https://basarat.gitbooks.io/typescript/docs/types/literal-
types.html)が参考になるかと思われます。

TypeScriptにおける _Literal Types_ とは、雑に言えば「リテラルとして存在する値 **固有**
の型」のようなものです。たとえば、`true`という **リテラル** は _literal true型_
の値`true`です。あるいは、`false`という **リテラル** は _literal false型_ の値`false`です。

[code]

    true === (true as true);
    false === (false as false);
    
[/code]

literal true/false型は明らかにprimitive
Booleanの派生型ですが、両者の比較に際してはそれぞれがtrueまたはfalseしかとらない固有の型であるために、異なる型を比較したような状態となり型エラーとなります。

このような比較の例として、特にBoolean,Number,String型のリテラルどうしの比較は（意味があるかはともかくとして）同様の理由によりTypeScriptでは許容されません。

[code]

    true === false;  // (true as true) === (false as false);
    1 === 2;         // (1 as 1) === (2 as 2);
    'foo' === 'bar'; // ('foo' as foo) === ('bar' as bar);
    // incompatible types
    
[/code]

では質問のコードですが、

[code]

    hoge==false ? ...
    
[/code]

において、比較演算子`==`の両辺の値がどのような型として扱われるかが問題です。  
右辺`false`は先述のとおりリテラル固有のliteral false型として解釈されます。  
一方左辺`hoge`は定義式に基づくとprimitive Boolean型です。もしこのとおりに解釈されたのであれば、派生型であるliteral
false型との比較は可能であり、エラーはでません。

[code]

    // もしhogeがboolean型であるとされた場合
    hoge==false
    (hoge as boolean) == ((false as false) as boolean)
    //       派生型のためキャスト（比較）可  ^^^^^^^^^
    
[/code]

一方で、質問のいずれのコードについても、処理を追えば比較部分に到達する時点で変数`hoge`の値は100%の確率で、つまり静的に`true`であることがわかります。これを考慮すればhogeの型も静的に決定可能であり、具体的にはliteral
true型です。TypeScriptの型システムにおいてこうした解釈がなされるとすれば、言及されているような型エラーが出ることは理解されます。

[code]

    var hoge:boolean;
    hoge = true;
    // ここに到達する時点で変数hogeがliteral true型であることは
    // 静的に（コードを実行しなくても）解析可能であるので、
    // boolean型と定義されているものの（より厳密に）literal true型として扱われる
    hoge==false ? ...
    // (hoge as true) === (false as false)
    //       ^^^^^^^ 比較不能
    
[/code]

こうした型を決定する静的解析システムは、TypeScriptにおいては[CFA](https://github.com/Microsoft/TypeScript/wiki/What's-new-
in-TypeScript#control-flow-based-type-analysis)とよばれています。

反対の例を挙げれば理解が深まるかもしれません。たとえばCFAが「実質的に」primitive Boolean型であるとみなしliteral
true/false型として扱わないケースは次のコードです:

[code]

    let hoge: boolean;
    // 次のコードは結果を静的に解析できない
    // => 「実質的に」boolean型として扱われる
    hoge = (Math.random() > 0.5);
    // 変数hogeはboolean型として解釈され、エラーは出ない
    hoge === false ? ...
    
[/code]

こうした取り組み、すなわち「より厳密に」「より実質的に」型をチェックすることで、native
jsでは膨大な検証コードを関数の冒頭に必要としたような状況をTypeScriptは解消しています。

上に上げたようなふるまいの是非など、この手の話題は[issue](https://github.com/Microsoft/TypeScript/issues)を覗くとよく議論されている（はず）ですので、興味があればご確認ください。

* * *

この存在価値がいまいちな _literal types_ ですが、よりプリミティブな実装の例としては _typeof operator_
の返り値の型がそうです。次のコードはこの質問のエラーとまったく同様のエラーになります:

[code]

    typeof hoge === 'nantokakanntoka'
    // Operator '===' cannot be applied to types '"string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"' and '"nantokakanntoka"'.
    
[/code]

これは、typeofの返り値が内部で _literal "string" | "number" | "boolean" | "symbol" |
"undefined" | "object" | "function"型_
と定義されているからです。便宜上typeofを関数のように書けば、次のような疑似コードで定義されているものと考えてください。

[code]

    function typeof(obj: any): "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" {
        // ...
    }
    
[/code]

この定義のありがたさとしては、native jsがtypeofの返り値をprimitive
String型としているためにまったく不正な型名`nantokakanntoka`との比較を許容してしまうことに対して、typescriptではliteral
typesを用いてenumのような型付けを簡単に実装できてしまい、かつ型名をいちいち名前をつけて定義していないところです。  
このようにliteral typesは「もうちょっとその型のなかでも値を制限したいけど型名をつくるのは大げさだしreturn
typeに書きたいし、」みたいなところで活躍するものと（主観では）思います。

以上、個人の理解に基づく内容ですので、誤り等あればご指摘ください。

