microsoft / microsoft/TypeScript

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

Aperta
#61,750 3 commenti 1 reazione 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Experimentation Needed Suggestion
Lingua principale
Go
Stelle
111k
Fork
14.3k
Merge medio
2g 4h
PR unite (30g)
132

Descrizione

🔍 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?

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia con l'esempio motivante doubleMap e il TypeScript Playground collegato, quindi analizza come vengono attualmente verificati l'accesso indicizzato a tuple generiche e l'iterazione for-of. Il lavoro è completato quando le chiamate ai mapper scambiate vengono rifiutate, mentre gli usi validi di tuple e array mantengono i tipi degli elementi appropriati T[number] o S[number].

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
typescript
Ambito
compilers
Tipo di issue
Funzionalità
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.