正確に言えば無効化ではなく、挙動の変化というか弱体化というか…どちらにしろ直感的な挙動では無かったのでメモ。
動機
以下の記事を読んで、とてもモヤモヤしたので調べてみました。
TypeScriptのnever型について - Qiita https://qiita.com/clown6613/items/042082f50534bafdc0a2
元々コメントで返すつもりだったのでそういう文体ですが、長くなりそうなので別記事にします。途中から一人語りのような書き方に変わっているような気がする…。
回答
極めて直感的ではないですが、tsconfig.json
に"strict": true
を入れることでエラーを出ないようにできます。VSCode標準のjsconfigではstrictNullChecks
とstrictFunctionTypes
のみがtrueとなっていますが、strict
をtrueにすることで多くのstrict系のオプションをまとめてtrueにすることができます。 https://www.typescriptlang.org/tsconfig#strict
strict
をtrueにしてトランスコンパイルすることが「最もTypeScriptらしいコード」を書く条件であるとされているため、おそらく参照された本でもstrict
をtrueにしているのでしょう。VSCodeでは、 以下のコミットでようやくstrictのうちの2オプションが追加されているので、おそらくstrictオプションは最低限としたいのでしょう。
Add setting to enable strict null checks in implicit JS files · Issue #109988 · microsoft/vscode https://github.com/microsoft/vscode/issues/109988
というか、VSCodeデフォルトのconfigだと、関数の引数に型書かなくても(noImplicitAny
が無いので)エラーにならないんですね。初めて知りました。
さて、strict
をtrueにすると、当然strictNullChecks
はtrueになりますが、他にもnoImplicitAny
もtrueになります。noImplicitAny
は「暗黙的なanyを排除する」オプションです。しかし、後方互換性の問題からnoImplicitAny
をtrueにした場合は、空の配列をany[]型として扱って、この配列に変更が加えられた場合に型推論を行うようです。以下がそのIssueのリンクです。
Empty array's type changes from never to any when turning on noImplicitAny · Issue #36987 · microsoft/TypeScript https://github.com/microsoft/TypeScript/issues/36987
後方互換性?
この後方互換性というのが実際に何かというのも調べたのですが、最低でも以下の現時点で8年前のIssueまでは遡ることが可能でした。noImplicitAny
はstrictNullChecks
を上書きしてしまうという認識で良いのかは分かりませんが、挙動上そんな感じです。
No widening in strict null checking mode by ahejlsberg · Pull Request #8944 · microsoft/TypeScript https://github.com/microsoft/TypeScript/pull/8944
Infer bottom type for type argument when passing empty array · Issue #8878 · microsoft/TypeScript https://github.com/microsoft/TypeScript/issues/8878
最後に挙動を再確認してみましょう。空の配列ではなく、再代入可能な変数で見ると分かりやすいです。これを空の配列にしてpushなどで変更しても、同じことが起きます。
strictNullChecks
がtrueの場合
noImplicitAny
をfalseにした場合
let a = null // aの型はnullになる
a = 'aaa' // エラー
noImplicitAny
をtrueにした場合
let a = null // aの型はanyになる
a = 'aaa' // string型になる
strictNullChecks
がfalseの場合
noImplicitAny
をfalseにした場合
let a = null // aの型はanyになる
a = 'aaa' // any型のまま
noImplicitAny
をtrueにした場合
let a = null // aの型はanyになる
a = 'aaa' // any型になる
これがモヤモヤの原因です。
しかし、どう考えてもstrictNullChecks
はtrueの方が良いのは明らかですよね。これが無いとこの書き方がanyまみれになってしまう。VSCodeのPRでもIntelliSenseのためと書かれています。
だからと言って、strictNullChecks
を今更優先させてしまうと多分阿鼻叫喚になるのは明らかで、strict
というおまとめオプションで縛られている以上breakingな変更もできないしと言うことで、残されているのでしょうか。
浅い理解&憶測が多いので、マサカリが飛んでくる可能性がありますが、解決方法は分かったのでとりあえずヨシとします。
書いてから気づきましたが、もっと細かいことが書かれている記事がありました。コメント返すだけならこのURLを書けばそれで良かったのでしょう。
let h = null
について、TypeScript はどのような型を推論するか - Qiita https://qiita.com/eyuta/items/31abebb2395c136e0591