microsoft / microsoft/TypeScript

Feature Request: "extends oneof" generic constraint; allows for narrowing type parameters

Aperta
#27,808 95 commenti 265 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

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

Descrizione

Search Terms

  • generic bounds
  • narrow generics
  • extends oneof

Suggestion

Add a new kind of generic type bound, similar to T extends C but of the form T extends oneof(A, B, C).

(Please bikeshed the semantics, not the syntax. I know this version is not great to write, but it is backwards compatible.)

Similar to T extends C, when the type parameter is determined (either explicitly or through inference), the compiler would check that the constraint holds. T extends oneof(A, B, C) means that at least one of T extends A, T extends B, T extends C holds. So, for example, in a function

function smallest<T extends oneof(string, number)>(x: T[]): T {
    if (x.length == 0) {
        throw new Error('empty');
    }
    return x.slice(0).sort()[0];
}

Just like today, these would be legal:

smallest<number>([1, 2, 3);        // legal
smallest<string>(["a", "b", "c"]); // legal

smallest([1, 2, 3]);               // legal
smallest(["a", "b", "c"]);         // legal

But (unlike using extends) the following would be illegal:

smallest<string | number>(["a", "b", "c"]); // illegal
// string|number does not extend string
// string|number does not extend number
// Therefore, string|number is not "in" string|number, so the call fails (at compile time).

// Similarly, these are illegal:
smallest<string | number>([1, 2, 3]);       // illegal
smallest([1, "a", 3]);                      // illegal

Use Cases / Examples

What this would open up is the ability to narrow generic parameters by putting type guards on values inside functions:

function smallestString(xs: string[]): string {
    ... // e.g. a natural-sort smallest string function
}
function smallestNumber(x: number[]): number {
    ... // e.g. a sort that compares numbers correctly instead of lexicographically
}

function smallest<T extends oneof(string, number)>(x: T[]): T {
    if (x.length == 0) {
        throw new Error('empty');
    }
    const first = x[0]; // first has type "T"
    if (typeof first == "string") {
        // it is either the case that T extends string or that T extends number.
        // typeof (anything extending number) is not "string", so we know at this point that
        // T extends string only.
        return smallestString(x); // legal
    }
    // at this point, we know that if T extended string, it would have exited the first if.
    // therefore, we can safely call
    return smallestNumber(x);
}

This can't be safely done using extends, since looking at one item (even if there's only one item) can't tell you anything about T; only about that object's dynamic type.

Unresolved: Syntax

The actual syntax isn't really important to me; I just would like to be able to get narrowing of generic types in a principled way.

(EDIT:)
Note: despite the initial appearance, oneof(...) is not a type operator. The abstract syntax parse would be more like T extends_oneof(A, B, C); the oneof and the extends are not separate.

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. new expression-level syntax)

(any solution will reserve new syntax, so it's not a breaking change, and it only affects flow / type narrowing so no runtime component is needed)

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 gli esempi dell'issue sui bound generici e confrontali con i vincoli generici esistenti di TypeScript e con il suo comportamento di narrowing del flusso di controllo. Il lavoro sarebbe completo una volta concordate sintassi e semantica per il narrowing dei parametri generici, seguito dal supporto nel compilatore e da test che coprano le chiamate proposte valide e non valide.

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à
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
30/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.