37823


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`の部分がエラーに  
なりません。  
この違いはどうして起こるのでしょうか？


CC BY-SA 3.0
2017-09-08T01:44:52.107
37793
2017-09-09T11:55:38.957
12195
question
typescript
TypeScriptで初期値を指定して変数を設定すると==演算子でエラーになる時がある


投稿の内容に誤りと思われる箇所がありましたので訂正します。また、[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に書きたいし、」みたいなところで活躍するものと（主観では）思います。

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


CC BY-SA 3.0
2017-09-09T00:39:59.633
37823
2017-09-09T11:55:38.957
2017-09-09T11:55:38.957
37793
answer


> このコードのように変数の宣言と代入を別のところで行うとhoge==falseの部分がエラーに  
>  なりません。

これは、2.2.1 の [_"Control flow based type
analysis"_](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-
TypeScript#control-flow-based-type-analysis) が不十分だからだと思います。  
2.5.2 においてはエラーになります。  
この点を以下で説明します。  
`(literal) true` 型 などについては[rio.irikami
さんの回答](https://ja.stackoverflow.com/a/37823/3054)を参照下さい。

### `boolean` 型は Union 型 `(true | false)` と等しい

これはドキュメントに記述を見つけられませんでしたが、実際の挙動や意味から考えて、`boolean` は Union 型なのだろうと思います。

### Union 型の変数には _"Control flow based type analysis"_ が行なわれる

_"Control flow based type analysis"_ というのは、型ガードだけでなく代入なども解析し、Union
型変数の可能性を狭めようという試みです。  
つまり、`boolean` 型として宣言された変数も、この解析の結果、場所によっては `true` 型や `false`
型と見なされる事がある、という事です。

[code]

    // 2.5.2 での動き
    function get_bool() :boolean {
        return (new Date().getSeconds()) >= 30
    }
    let bool = get_bool()  // bool は boolean 型
    let true_or_42 :(true | 42)
    
    true_or_42 = bool   // エラー
    // ここでは bool は true かも知れず、false かも知れないので宣言時の boolean のまま
    // false の可能性があるので (true | 42) には代入できない
    // => error TS2322: Type 'boolean' is not assignable to type 'true | 42'.
    
    bool = true
    true_or_42 = bool   // OK
    // ここでは bool は true でしかありえないので、true 型 になる
    // よって、(true | 42) に代入可能
    
[/code]

### 比較演算と _"Control flow based type analysis"_ の関係

比較演算子の両辺の型は代入互換性（少なくともどちらかの型が、もう片方の型に代入可能であること）が求められます。  
参照: ["The <, >, <=, >=, ==, !=, ===, and !==
operators"](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#4.19.3)

[code]

    true == true        // OK
    // true 型と true 型なので相互に代入可能で比較可能
    
    get_bool() == true  // OK
    // true 型に boolean 型 を代入することはできないが、
    // 逆（boolean 型に true 型を代入する事）は可能なため比較可能
    
    false == true       // エラー
    // false 型の変数に true 型の値は代入できず、逆も同じなので、この比較は許可されない
    // => error TS2365: Operator '==' cannot be applied to types 'false' and 'true'.
    
[/code]

ですので、質問のコードで `hoge==false` が行なわれる際に `hoge` 変数がどういった型として見なされているかが重要です。

[code]

    var hoge:boolean = true;
    
[/code]

の後であれば `true` が代入済みであるので、 _"Control flow based type analysis"_ により、`hoge` は
`true` 型と見なされ、`false` との比較でエラーになるわけです。

問題の、

[code]

    var hoge:boolean;
    hoge = true;
    
[/code]

ですが、2.2.1 では2行目で代入される値が `true` にしか成りえない事を解析できないのだと思われます。  
そうすると、`hoge` の型を狭められないため、この代入後も宣言に従い `boolean` 型と見なされます。  
`boolean` 型には `false` 型を代入できますから、比較可能になり `hoge==false` がエラーになりません。

2.5.2 では _"Control flow based type analysis"_ がよりよく働き、`hoge` が `true`
にしかなりようがない事を解析し、代入以降 `true` 型 と見なされるようになり、 `hoge==false` がエラーになります。

### バグか

> これはエラーにならないのがバグということですか。

バグと言うべきかは解りませんが、過去のバージョンでは _"Control flow based type analysis"_
の能力が低く、エラーを検出できなかったという事だと思います。


CC BY-SA 3.0
2017-09-09T03:36:36.223
37825
2017-09-09T03:45:57.003
2017-09-09T03:45:57.003
3054
3054
37793
answer