microsoft / microsoft/TypeScript

Request: keyword or operator for iterating unions

Offen
#43,694 4 Kommentare 6 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Awaiting More Feedback Suggestion
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.3k
Ø Merge
2 T. 4 Std.
Gemergte PRs (30 T.)
132

Beschreibung

Suggestion

I'm sorry, I feel like this has definitely been suggested but I couldn't find it in issues.

I think there should be a more explicit way to force iterating through types of a union in generics than what we currently have. The easiest example I can think of is the way keyof interacts with unions.

interface A {
  foo: string;
}

interface B {
  bar: number;
}

type C = A | B;

type Keys = keyof C; // never

I understand why it works this way. I'm not arguing that TypeScript is doing anything wrong. However, sometimes I want to get all the keys of all the members of a union, and the workaround is to abuse conditional types to force iteration.

type KeyofUnion<T> = T extends T ? keyof T : never;

type Keys = KeyofUnion<A | B>; // 'foo' | 'bar';

The expression T extends T ? /* something */ : never is silly enough that I feel it justifies creating a keyword. More importantly, it's not clear that types would behave this way if you didn't look it up. Why would conditional types force union iteration? I assume there is a good reason, but for someone not super familiar with TS, I would never expect them to see that and understand why anyone wrote it.

I believe this suggestion would be an improvement under #7 of the "non-goals" of the contributing guidelines.

🔍 Search Terms

Iterating unions, distributing unions, T extends T ? T : never

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Add a keyword that explicitly iterates types in a union instead of relying on conditional types.

type A {
  foo: string;
}
type B {
  bar: number;
}
type C = A | B;

type Keys = keyof in C;
type Keys = keyof union C;
type Keys = keyof memberof C;

I'm not happy with any of the ideas I have for the syntax but hopefully you can see what I'm going for.

📃 Motivating Example

I already put one example in the first section, but I'll also give a more concrete example of something I was trying to do that made me think of this.

I wanted to create a generic type that takes in two args.

  • T, an object
  • and F, a type that exists as a member on T.

The resulting type is a union of all keys in T whose members are type F.

Example

interface Foo {
  a: string;
  b: number;
  c: Date;
  d: string;
  e: string;
  f: number;
}

type NumberKeys = KeysOfType<Foo, number>; // 'b' | 'f';
type StringKeys = KeysOfType<Foo, string>; // 'a' | 'd' | 'e';
type BoolKeys = KeysOfType<Foo, boolean>; // Error: Type 'boolean' does not satisfy the constraint 'string | number | Date'.

This was my first version of this type

type KeysOfType<T, F extends T[keyof T]> = {
  [K in keyof T]: T[K] extends F ? K : never;
}[keyof T];

But, when I tried to pass a union in as the first argument, it didn't work the way I wanted it to. This led me to change it to this:

type KeysOfType<T, F extends T extends T ? T[keyof T] : never> = T extends T
  ? {
      [K in keyof T]: T[K] extends F ? K : never;
    }[keyof T]
  : never;

This is much more cumbersome.

I'd love to see a way to force this without abusing conditional types.

type KeysOfType<T, F extends T[keyof in T]> = {
 [K in keyof in T]: T[K] extends F ? K : never;
}[keyof T];

type KeysOfType<T, F extends T[keyof union T]> = {
 [K in keyof union T]: T[K] extends F ? K : never;
}[keyof T];

Again, I really don't have any good ideas for the syntax, but hopefully the spirit of this request is clear at this point.

💻 Use Cases

I'm having a hard time thinking of another use case than the one I provided here, so maybe it's edge enough to be too low priority. I still think it'd be nice to have an explicit way to do this.

Thanks for reading this and have a good day.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne damit, den Workaround für bedingte Typen des Issues, das Verhalten von keyof bei Unions und die verlinkten TypeScript-Designziele zu prüfen. Vergleiche die vorgeschlagenen Syntaxideen in, union und memberof mit den zugrunde liegenden Beispielen. Als erledigt gilt die Aufgabe erst, wenn ein abgestimmtes Sprachdesign und ein Implementierungsumfang festgelegt sind; beides ist in diesem Issue noch nicht spezifiziert.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
typescript
Bereich
compilers
Issue-Typ
Feature
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
25/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.