microsoft / microsoft/TypeScript
Feature Request: "extends oneof" generic constraint; allows for narrowing type parameters
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
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)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the issue's generic-bound examples and compare them with TypeScript's existing generic constraints and control-flow narrowing behavior. Done would require an agreed syntax and semantics for narrowing generic parameters, followed by compiler support and tests covering the proposed legal and illegal calls.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100