Skip to content

Type inference fails with immediate type guards #18562

Description

TypeScript Version: master / 2.6.0-dev20170914 / whatever the playground has currently
I searched for immediate type guard and type guard inference , I hope I didn't miss an existing issue.

The issue is that the type inference fails for overloads with type guards such as the one for Array.filter when the type guard is passed as an immediate value. See also #7657 The problem seems to be that TS infers the type of the argument e of the type guard from the type of the callback parameter of filter. In doing so, the return type of type guard is lost. If you specify the argument e of the type guard explicitly, it works as expected.

Code

// for ref the Array signature.
interface Array<T> {
    filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
}

declare const arr: (string | number)[]

// Property 'substring' does not exist on type 'string | number'.
arr.filter((e): e is string => 'string' == typeof e)[0].substring(0, 10);

arr.filter((e: any): e is string => 'string' == typeof e)[0].substring(0, 10); // ok

const isStringArrow = (e: any): e is string => 'string' == typeof e;
arr.filter(isStringArrow)[0].substring(0, 10); // ok

// Property 'substring' does not exist on type 'string | number'.
arr.filter(function (e): e is string { return 'string' == typeof e })[0].substring(0, 10);

// Property 'substring' does not exist on type 'string | number'. 
arr.filter(function (e: any): e is string { return 'string' == typeof e })[0]
    .substring(0, 10);

arr.filter(function (this: void, e: any): e is string { return 'string' == typeof e })[0]
    .substring(0, 10); // ok

const isString = function (e: any): e is string { return 'string' == typeof e }; 
arr.filter(isString)[0].substring(0, 10); // ok

playground

Expected behavior:
All cases should work.
Actual behavior:
See above.

Activity

  1. dgreene1 commented on Feb 27, 2018

    @dgreene1

    Should wait on #17600 before working on this.

    Since #17600 got merged, can we pick this ticket up again?

    Please let me know how I could help.

    Example of the workaround, which does not scale well:

    // Still resolves to (string | undefined)[]
    var result = ["hello", undefined].filter(x => x != null);
    
    // I had to work harder, but I eventually got TypeScript to believe that it's a string[]
    function isString(test: any): test is string{
        return typeof(test) === "string";
    }
    var annoyinglyAchievedResult = ["hello", undefined].filter(isString);
    

    Many of us are just used to writing this like !!x

    P.S. As always, thank you so much for the wonderful work you're all doing. :)

  2. emilio-martinez commented on May 2, 2018

    @emilio-martinez

    Andy (Andrewkraft) (@Andy-MS) can this be picked up again? Filter functions under strictNullChecks are a pain to workaround at this point

  3. mhegazy commented on May 2, 2018

    @mhegazy
    Contributor

    The examples in the OP should all now be working with #5101 fixed.

    The examples in #18562 (comment) require the type guards to be automatically inferred, which is tracked by #16069

  4. typescript-bot commented on May 16, 2018

    @typescript-bot
    Contributor

    Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

  5. locked and limited conversation to collaborators on Jul 31, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions