メインコンテンツまでスキップ

strictFunctionTypes

strictFunctionTypesは引数型の変性のチェックを厳しくするコンパイラオプションです。

  • デフォルト: strictが有効の場合はtrue、それ以外はfalse
  • 追加されたバージョン: 2.6
  • TypeScript公式が有効化推奨

引数の双変性は安心できない

TypeScriptの関数には引数の双変性(parameter bivariance)という性質があります。どういうことか、順を追って見ていきましょう。

まず、次の3つの型の範囲を考えてみましょう。

  1. number
  2. number | null
  3. number | null | undefined

numbernumber | nullより狭い型です。number | nullの範囲には10.5などの数値型とnull型があります。number型の範囲にあるのは数値型だけです。最後のnumber | null | undefinedはこの中でもっとも範囲が広い型です。

範囲の広さ取れる値の例
number狭い10.5...
number | null広い10.5...、null
number | null | undefinedより広い10.5...、nullundefined

続いて、次の変数funcについて考えてみましょう。この変数の型は、引数にnumber | nullを取る関数です。

ts
let func: (n: number | null) => any;
ts
let func: (n: number | null) => any;

この変数funcに代入できる値はどんな型でしょうか。当然、型注釈と同じ関数は問題なく代入できます。

ts
func = (n: number | null) => {}; // OK
ts
func = (n: number | null) => {}; // OK

引数number | nullより広いnumber | null | undefinedを受ける関数は代入できるでしょうか。これも大丈夫です。

ts
func = (n: number | null | undefined) => {}; // OK
ts
func = (n: number | null | undefined) => {}; // OK

このような引数型の範囲を広められる特性を引数の反変性(parameter contravariance)と言います。

引数number | nullより狭いnumberを取る関数は代入できるでしょうか。これもTypeScriptでは代入できます。

ts
func = (n: number) => {}; // OK
ts
func = (n: number) => {}; // OK

このような引数型の範囲を狭められる特性を引数の共変性(parameter covariance)と言います。

TypeScriptの関数型は、引数の反変性と引数の共変性の両特性を持っています。この両特性は一言で、引数の双変性と言います。

引数の双変性は危険な側面があります。nullが渡せるfunc関数に、numberだけが来ることを前提とした関数を代入しているためです。もしも、funcnullを渡すと、実行時エラーが発生します。

ts
// nullも来る可能性がある関数型
let func: (n: number | null) => any;
// numberを前提とした関数を代入
func = (n: number) => n.toString();
// funcにはnullが渡せる → 矛盾が実行時エラーを生む
func(null);
Cannot read properties of null (reading 'toString')
ts
// nullも来る可能性がある関数型
let func: (n: number | null) => any;
// numberを前提とした関数を代入
func = (n: number) => n.toString();
// funcにはnullが渡せる → 矛盾が実行時エラーを生む
func(null);
Cannot read properties of null (reading 'toString')

こうした実行時エラーが起きないようにするには、引数型は反変だけが許されるべきです。そして、もし共変ならコンパイルエラーで知らせてほしいところです。ところが、TypeScriptは引数型は双変(つまり共変もOK)であるため、安心できない仕様になっています。

引数の共変性を許さないstrictFunctionTypes

上の課題を解決するのが、コンパイラオプションstrictFunctionTypesです。これをtrueにすると、引数が反変になります。もし、共変の引数にした場合、TypeScriptが警告を出します。

ts
let func: (n: number | null) => any;
// 不変
func = (n: number | null) => {}; // OK
// 反変
func = (n: number | null | undefined) => {}; // OK
// 共変
func = (n: number) => {}; // NG
Type '(n: number) => void' is not assignable to type '(n: number | null) => any'. Types of parameters 'n' and 'n' are incompatible. Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.2322Type '(n: number) => void' is not assignable to type '(n: number | null) => any'. Types of parameters 'n' and 'n' are incompatible. Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.
ts
let func: (n: number | null) => any;
// 不変
func = (n: number | null) => {}; // OK
// 反変
func = (n: number | null | undefined) => {}; // OK
// 共変
func = (n: number) => {}; // NG
Type '(n: number) => void' is not assignable to type '(n: number | null) => any'. Types of parameters 'n' and 'n' are incompatible. Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.2322Type '(n: number) => void' is not assignable to type '(n: number | null) => any'. Types of parameters 'n' and 'n' are incompatible. Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.

strictFunctionTypesは思いがけない実行時エラーを防ぐのに役立ちます。strictFunctionTypestrueを設定するのがお勧めです。

メソッド型はチェックされない

strictFunctionTypesのチェックが働くのは関数型だけです。メソッド型には働きません。

ts
interface Obj {
// メソッド型
method(n: number | null): any;
}
const obj: Obj = {
method: (n: number) => {}, // チェックされない
};
ts
interface Obj {
// メソッド型
method(n: number | null): any;
}
const obj: Obj = {
method: (n: number) => {}, // チェックされない
};

インターフェースのメソッドでも、関数型で定義されたメソッドstrictFunctionTypesのチェックが働きます。

ts
interface Obj {
// 関数型
method: (n: number | null) => any;
}
const obj: Obj = {
method: (n: number) => {}, // チェックが働く
Type '(n: number) => void' is not assignable to type '(n: number | null) => any'. Types of parameters 'n' and 'n' are incompatible. Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.2322Type '(n: number) => void' is not assignable to type '(n: number | null) => any'. Types of parameters 'n' and 'n' are incompatible. Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.
};
ts
interface Obj {
// 関数型
method: (n: number | null) => any;
}
const obj: Obj = {
method: (n: number) => {}, // チェックが働く
Type '(n: number) => void' is not assignable to type '(n: number | null) => any'. Types of parameters 'n' and 'n' are incompatible. Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.2322Type '(n: number) => void' is not assignable to type '(n: number | null) => any'. Types of parameters 'n' and 'n' are incompatible. Type 'number | null' is not assignable to type 'number'. Type 'null' is not assignable to type 'number'.
};
学びをシェアする

⚙️TypeScriptのstrictFunctionTypesは、引数型の変性のチェックを厳しくするコンパイルオプション
☹️TypeScriptの引数は双変で安心できない
🔥実行時エラーが起こることも
✅strictFunctionTypesは反変にしてくれる
👍有効化推奨のオプション

『サバイバルTypeScript』より

この内容をツイートする

関連情報

📄️ strict

strict系のオプションを一括で有効化する
  • 質問する ─ 読んでも分からなかったこと、TypeScriptで分からないこと、お気軽にGitHubまで🙂
  • 問題を報告する ─ 文章やサンプルコードなどの誤植はお知らせください。