ts strictモードでParameters <Func>がunknown [](Array <unknown>)を拡張しないのはなぜですか

2020-06-01 typescript types arguments extends spread-syntax

タイトルは、ほとんどすべてを本当に語っています。私はこのコードを持っています:

  type testNoArgsF = () => number;
  type testArgsF = (arg1: boolean, arg2: string) => number;
  type unknownArgsF = (...args: unknown[]) => number;
  type anyArgsF = (...args: any[]) => number;

  type testII = testArgsF extends anyArgsF ? true : false; // true
  type testIII = Parameters<testArgsF> extends Parameters<unknownArgsF>
    ? true
    : false; // true

  // unexpected:
  type testIV = testArgsF extends unknownArgsF ? true : false; // false <- why?
  // even though:
  type testV = testNoArgsF extends unknownArgsF ? true : false; // true

typescript(バージョン3.8)で書かれており、strictモードを有効にしています。予期しない結果は、テスト関数がunknown[]スプレッド引数を持つ関数型を拡張しないことですが、パラメーターを確認しただけでは、それら unknown[]拡張します。戻り値の型は常に数値であるため、 extendsステートメントを改ざんするために他に何が異なる可能性があるのか​​理解できません。

その他の注意事項:

  • extendステートメントは、テスト関数の引数が0の場合にのみ真になります。
  • strictモードをオフにすると、この動作は見られません。

Answers

type testIV = testArgsF extends unknownArgsF ? true : false; // false 
type testIVa = unknownArgsF extends testArgsF  ? true : false; // true!

// because
type testIII = Parameters<testArgsF> extends Parameters<unknownArgsF>
    ? true
    : false; // true

Extendsは本当に「割り当て可能」を意味します。 また、BがAに割り当て可能な場合、 f(p:A)=>f(p:B)=>割り当て可能です。「反変」と呼ばれていると思います。

// Imagine your function expects callback that has some type as a paramter:

var f = (cb: ({ a, b }: { a: number, b: number }) => void) => { cb({ a:1, b: 1}); };

// you can safely call it giving it the function that accepts parameter 
// that the original parameter is assignable to but not the other way around
// because that's the way to guarantee that when our function f calls the callback
// it will provide sufficient amount of data
f(({ a }: { a: number }) => { })


var x = { a: 1, b: 1 };
var y = { a: 1 }

x = y; // error because x would have no b property after that assignment
y = x; // works because y has all it needs after that assignment and it doesn't matter it has property b too

// in other words:
type F = { a: number } extends { a: number, b: number } ? true : false; // false
type T = { a: number, b: number } extends { a: number } ? true : false; // true

// read 'extends' as 'is assignable to'

--strictFunctionTypesコンパイラオプションを有効にすると、関数型パラメータが反変的にチェックされます。 「反変」とは、関数のサブタイプの関係が関数パラメーターのそれとは逆の方向に変化することを意味します。したがって、 A extends B場合、 (x: B)=>void extends (x: A)=>void あり、その逆ではありません

これは、TypeScriptの「代入性」の性質( 動作サブタイピングとも呼ばれる)によるタイプセーフティの問題です。 A extends Bする場合は、 ABとして使用できるはずです。できない場合は、 A extends Bはありません。

--strictをオフにすると、コンパイラはTS-2.6より前の関数パラメータを二変量でチェックする動作を使用します。これは安全ではありませんが、生産性の理由で許可されていました。これはトピックから外れているかもしれませんが、TypeScript FAQエントリの「関数パラメーターが二変である理由」の詳細を読むことができます


とにかく、 unknownパラメーターをいくつでも受け入れる関数型が必要な場合、 unknown特定のサブタイプのみを持つ関数を安全に使用することはできません。観察する:

const t: testArgsF = (b, s) => (b ? s.trim() : s).length
const u: unknownArgsF = t; // error!

u(1, 2, 3); // explosion at runtime! s.trim is not a function

testArgsF extends unknownArgsFをtrueにtestArgsF extends unknownArgsF場合、エラーなしでtを上記のuに割り当てることができ、 u string 2番目の引数を喜んで受け入れると、すぐにランタイムエラーが発生します。

関数型をサブタイプ/実装する唯一の安全な方法は、サブタイプ/実装が、スーパータイプ/呼び出しシグネチャが期待するものと同じかそれより広い引数を受け入れることであることがわかります。そのため、-- --strictFunctionTypesが言語に導入されました。


あなたが変更した場合はunknownへのany (使用anyArgsFの代わりunknownArgsFので、コンパイラは文句を言わないであろうanyある意図的に不健全活字体インチ型は、 any およびすべての他のタイプ両方から割り当て可能であると考えられます。たとえば、 string extends anyany extends numberはどちらもtrueですが、 string extends numberはfalseであるため、これは安全でstring extends anyありany extends number 。したがって、上記の代替原則は、 anyが関与している場合は適用されません。値にany型の注釈を付けることは、その値の型チェックを緩めるまたはオフにすることと同じです。それはランタイムエラーからあなたを救うわけではありません。コンパイラのエラーを黙らせるだけです:

const a: anyArgsF = t; // okay, type checking with any is disabled/loosened
a(1, 2, 3); // same explosion at runtime!

testNoArgsF extends unknownArgsFtestNoArgsF extends unknownArgsFする場合がtrueの場合、これは代入性の結果でもあります。 (通常)渡された引数をすべて無視することになるため、引数を取らない関数を、ほぼすべての関数型であるかのように使用できます。

const n: testNoArgsF = () => 1;
const u2: unknownArgsF = n; // okay
u2(1, 2, 3); // okay at runtime, since `n` ignores its arguments

これはTypeScript FAQエントリで説明されています。「より少ないパラメータを持つ関数が、より多くのパラメータを取る関数に割り当てられるのはなぜですか?」


さて、それが役に立てば幸いです。幸運を!

コードへの遊び場リンク

簡単に説明します。「拡張」->「互換」->「代わりに使用できます」

(arg1: boolean, arg2: string) => number;を使用できますか?
(...args: unknown[]) => number代わりに(...args: unknown[]) => number

いいえ、後者は引数なしで呼び出しを処理できますが、前者は実行時に失敗する可能性があるためです(たとえば、引数のプロパティにアクセスしようとします)。

機能の互換性に関する詳細はこちら

Related