microsoft / microsoft/TypeScript

Tuples: maintaining type-param in the type of it's items

オープン
#61,750 コメント 3 件 リアクション 1 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Experimentation Needed Suggestion
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
2日 4時間
マージ済み PR(30日)
132

説明

🔍 Search Terms

for-of, T[number], tuple (I couldn't find more specific search terms).

✅ Viability Checklist
⭐ Suggestion

When a function takes an array/tuple as a type-parameter, e.g.

function foo<T extends readonly unknown[]>(items: T): void;

When accessing the items of this array (items[0]), they are "resolved" to the type defined for the generic, e.g., unknown in this example, but should be kept unresolved so that they show up as T[number] | undefined.

This becomes an issue once a function takes more than one array like this, for example, if it wants to use the tuple/array shape as a param in the output. This can lead to real runtime errors where TypeScript isn't providing enough safety.

This is also relevant for for-of loops iterating over items

for (const item of items) {
  console.log(item);
  //          ^? unknown, can be T[number]
}
📃 Motivating Example

The following example passes typescript checking but fails in runtime and throws exceptions when trying to access methods of strings on a number, and vice-versa.

type DoubleMap<
  T extends readonly unknown[],
  S extends readonly unknown[],
  RT,
  RS,
> = [...{ [I in keyof T]: RT }, ...{ [I in keyof S]: RS }];

function doubleMap<
  T extends readonly unknown[],
  S extends readonly unknown[],
  RT,
  RS,
>(
  t: T,
  s: S,
  mapperT: (item: T[number]) => RT,
  mapperS: (item: S[number]) => RS,
): DoubleMap<T, S, RT, RS> {
  const output: (T[number] | S[number])[] = [];

  for (const itemT of t) {
    // Oops, we got the mappers wrong, we are calling mapperS with an item from T
    output.push(mapperS(itemT));
  }

  for (const itemS of s) {
    // ...And here we are calling mapperT with an item from S
    output.push(mapperT(itemS));
  }

  // @ts-expect-error [ts2322] -- TypeScript can't tell we finished building the output, this is fine...
  return output;
}

const result = doubleMap(
  //  ^? [boolean, boolean, boolean, number, number, number]
  ["a", "b", "c"] as const,
  [1, 2, 3] as const,
  (item) => item.startsWith("a"),
  (item) => item.toPrecision(1),
);

https://www.typescriptlang.org/play/?noUncheckedIndexedAccess=true&noUnusedLocals=true&noUnusedParameters=true&noPropertyAccessFromIndexSignature=true&noImplicitOverride=true&noFallthroughCasesInSwitch=true&exactOptionalPropertyTypes=true#code/C4TwDgpgBAIg9gVwEYBsIFkCGYA8AoKKAFSggA9gIA7AEwGcoAnCTGuKlEKBKgayrgB3KgG0AugBoCUAMqkK1ekxZsOXHvyGjJ0gEpEphXTKkA+KAF4oIgHR2A3tYCSUAJZUovCCDgAzYmIAXFD6UAC+ElB2No4iLu6e3n6yQSFyYWIA3Hh4vjwAxsCu7FBsyGhYuNIk5JS0DMys7JzcfALC4oay8nVKjaotGu3aXfqjJnimABTSwMEG0nTBE4QAttiQjETBU66Uq-MiVAirSBCMYgCUluZj0utgmzI7exAHskcnZxfXFrcTl2C8HKGGwOAMskiYzS5ns0ny7DowCgiGAYAQcygUyIn1O5zEUAAPh9jnifuJLNYsjlCL44IwsQiqEi3PsSMlgNc4YRCAB6XlQADycDAdEigmgAHM4MjgAALaAPTYMQSMdiS8XQTDMKD5TAoFDuSVQJXnOSCPZyqCYDyvVZQXxq+1EaSEVHo4A2dF0OVTU2MGS7NmXS7ZQhhGkO+mMxHIu1yZJ0LmuqD8qJ2ACCtCgCp1EutOr1BqNJo25xIFvl1tt+wdTtkKfdGK9CB9frLWyDbxkIbD4UjaYAAsA6ABaciQQrjxhqhkiEcAJgAzAuFwTR6PiOAIDJ8oxXGBkXqqAByWUQA1QfO+dyuH0QGhQJAIVwoGgl+XQJvASLyu9uBgbyoCBomkZhgAQRgPG-bIIzwJkWWYOgEBQZErDKVBQTAGY+QFKAAD0AH5pBEAAiTBSMiUikEoqBSPyUiCUwBgEJ-EiAEZIgXSIlyYljYy6LtVl+cw7RsJFtRHAB1S0pnI0jLkEu0RNZN4bGAOAAAVmHyO9iioKZ2MUvBQzwIA

Live example:

https://codesandbox.io/p/sandbox/tgr8pm

💻 Use Cases
  1. What do you want to use this for?
  2. What shortcomings exist with current approaches?
  3. What workarounds are you using in the meantime?

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

動機となる doubleMap の例とリンク先の TypeScript Playground から始め、ジェネリックなタプルのインデックス付きアクセスと for-of イテレーションが現在どのようにチェックされているかを調査します。入れ替えられた mapper の呼び出しが拒否され、有効なタプルおよび配列の使用では適切な要素型 T[number] または S[number] が引き続き維持されれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
typescript
領域
compilers
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。