Skip to content

disallow impossible type predicates (or give them a type other than never)Β #61502

@kirkwaiblinger

Description

@kirkwaiblinger

πŸ” Search Terms

type predicates, always false, intersection,

βœ… Viability Checklist

⭐ 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

  1. What do you want to use this for? prevent mistakes
  2. What shortcomings exist with current approaches? unsafe code is allowed
  3. What workarounds are you using in the meantime? considering trying to implement in the linter, but not sure if it's feasible

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions