Repository: TypeScript
Issue: Feature Request: "extends oneof" generic constraint; allows for narrowing type parameters
Labels: Suggestion,In Discussion

## Search Terms

* generic bounds
* narrow generics
* extends oneof


<!-- List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily -->

## 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

```ts
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:

```ts
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**:

```ts
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:

```ts
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:
* [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] 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)