-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Description
π Search Terms
type predicates, always false, intersection,
β Viability Checklist
- 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, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
I propose that this code should be an error because s cannot be a number. (playground)
declare const s: string;
declare function isNumber(x: string | number): x is number;
declare function doSomethingWithString(x: string): void;
if (isNumber(s)) {
doSomethingWithString(s); //<-- totally unsafe
// ^? never
} This could be handled analogously to
// TS ERROR: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
s as number; For example, TS ERROR: Use of type predicate to check whether 'string' is a 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'string | number' first..
never being the narrowed type of s inside the branch allows completely unsafe assignments that are not necessarily straightforward to notice visually (as shown in the code example). These might occur organically by accidentally swapping the true and false branches of the predicate. Another alternative to hard-erroring would be to have the narrowed type of s be number instead of string, which would be safe (though I'm not sure whether an identifier is ever allowed to be "narrowed" to a type that is not a subset of its declared type)
FYI - at first I thought about implementing this in typescript-eslint (typescript-eslint/typescript-eslint#10997) but I'm not sure that we have the requisite APIs to do so, since I don't think that checker.isAssignableTo() is sufficient.
π Motivating Example
declare const s: string;
declare function isNumber(x: string | number): x is number;
declare function doSomethingWithString(x: string): void;
if (isNumber(s)) {
doSomethingWithString(s); //<-- totally unsafe
// ^? never
} π» Use Cases
- What do you want to use this for? prevent mistakes
- What shortcomings exist with current approaches? unsafe code is allowed
- What workarounds are you using in the meantime? considering trying to implement in the linter, but not sure if it's feasible