microsoft / microsoft/TypeScript
`typeof` expressions should return an equivalent of a guard
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
Search Terms
typeof guard array filter
Suggestion
Currently, typeof expressions return a boolean as expected. However, they are also special in that they narrow the types of their operands. In a similar vein, we can use type guards as the return value of functions to narrow(or assert) the types of their parameters:
declare const broad: string | number | object;
if(typeof broad === 'number') {
// broad is now a number, not a string nor an object
}
declare function isNumber(param: any): param is number;
if(isNumber(broad) {
// broad is now a number, not a string nor an object
}
This is all fine, but suppose you write a very specific function:
// Current return type: boolean
// Wish it was `param is number`
const isNumber = (param: any) => typeof param === 'number'
if (isNumber(broad)) {
// param is possibly a string or object?!
}
This is IMO a special case that should be handled; particularly, when a typeof expression(or any expression that narrows a parameter's type) is the return value of a function, TS should infer that the function is a guard.
Notice that, unlike the discussion in #6015, this issue is about the inferred type of a function which has a return value of a narrowing-expression. We could go further:
const isObject = (o: unknown) => typeof o === 'object';
// type for isObject == (o:unknown) => o is object
const isElement = (o: object) => o instanceof Element;
// type for isElement == (o:object) => o is Element
const unknownIsElement = (o: unknown) => isObject(o) && isElement(o);
// type for unknownIsElement == (o:unknown) => o is Element
const numberIsElement = (o:unknown) => isNumber(o) && isElement(o); // error: o is number so isElement can't be called
(by the way, I searched but couldn't find a similar one. If this is a duplicate I apologize!)
Use Cases
The most immediate use case is for array filter:
// error: (string|object)[] not assignable to string[]
const stringElements: string[] = elements.filter(e => typeof e === 'string');
// Works, but repetitive
const stringElements: string[] = elements.filter((e): e is string => typeof e === 'string');
I'm sure this type of change would make guards far more powerful as well, since we could create functions combine JS-checks(typeof, instanceof) or even TS-checks(like discriminant variable checking) without having to explicitly write the guard expression(which, IMO is as good as a type assertion at the moment).
Examples
(see above)
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, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
This is possibly a breaking change, but IMO a good one as it would highlight actual programming errors for code that breaks
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 by investigating the TypeScript compiler areas responsible for inferred function return types and control-flow narrowing, using the typeof, instanceof, composed-guard, and Array.filter examples in the issue as behavioral cases. The work is done when inferred predicates narrow values as shown without changing emitted JavaScript, with the proposed error behavior also covered.
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
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100