Repository navigation
Permit functions that return a value to also serve as a type guard #31376
Description
Search Terms
Linear type, affine type, type guard
Suggestion
It would be very helpful to allow a function to serve as a type guard, but also return an unrelated value.
Use Cases
This can be used to express type changes as a result of mutating operations, covering some of the use cases of e.g. Rust's affine types. (See also #16148.)
Examples
Consider this example, compiled with --strictNullChecks:
type NonEmptyArray<T> = {
pop(): T;
} & Array<T>;
function isNonEmpty<T>(array: Array<T>): array is NonEmptyArray<T>;
function isNonEmpty(array: Array<unknown>): boolean {
return array.length > 0;
}
let array: string[] = ['element'];
if (isNonEmpty(array)) { // Guard gives 'array' type NonEmptyArray<string>.
const elem1: string = array.pop(); // Works. This is correct.
const elem2: string = array.pop(); // Also works, but elem2 will be undefined at runtime!
}We could make this correct if pop() could both return a value and behave as a type guard. This isn't great syntax, but nonetheless consider if this was supported:
type NonEmptyArray<T> = {
pop(): T && this is Array<T>;
} & Array<T>;
function isNonEmpty<T>(array: Array<T>): array is NonEmptyArray<T>;
function isNonEmpty(array: Array<unknown>): boolean {
return array.length > 0;
}
let array: string[] = ['element'];
if (isNonEmpty(array)) { // Guard gives 'array' type NonEmptyArray<string>.
const elem1: string = array.pop(); // Returns a string and gives 'array' type Array<string>.
const elem2: string = array.pop(); // Doesn't compile; pop() returns 'string | undefined'!
}Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
This can use a new, previously invalid syntax to avoid affecting any existing program.
- 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, etc.)
There's no change in the code that's emitted; this feature would exist purely at the level of the type system.
- This feature would agree with the rest of TypeScript's Design Goals.
I believe that it would. It seems to be aligned well, in particular, with "Statically identify constructs that are likely to be errors."
Activity
[-]Permit functions that return a value to serve as a type guard[/-][+]Permit functions that return a value to also serve as a type guard[/+][-]Permit functions that return a value to also serve as a type guard[/-][+]Permit functions that return a value to also serve as a type assertion[/+][-]Permit functions that return a value to also serve as a type assertion[/-][+]Permit functions that return a value to also serve as a type guard[/+]AlCalzone commentedon May 16, 2019
Could this be a solution to #18781 ?
sethfowler commentedon May 16, 2019
That one could be solved today, I think; seems like the blocker there is getting consensus that it's a good idea.
The related #13086 could definitely be solved by this, though. In that one, the issue is this:
The ideal thing would be to narrow the type of
mapsuch thatmap.get('key')would returnstringinstead ofstring | undefined. As folks pointed out in #13086, though, the problem is that you could callmap.delete('key')between the call tomap.has('key')andmap.get('key'). The idea in this issue, or some variant of it, could solve that by widening the type ofmapagain when you callmap.delete('key'). (Or by narrowing it such thatmap.get('key')returns the typeundefined.)