Skip to content

Partial type fails to narrow in spite of check for undefined #29496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
NoelAbrahams opened this issue Jan 20, 2019 · 8 comments
Closed

Partial type fails to narrow in spite of check for undefined #29496

NoelAbrahams opened this issue Jan 20, 2019 · 8 comments
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@NoelAbrahams
Copy link

NoelAbrahams commented Jan 20, 2019

The code below works in TS 3.0.

TypeScript Version: 3.2.1.0

Search Terms: is:issue 3.2 Partial

Code

declare function never(value: never): never;

    const enum BarEnum {
        bar1 = 1,
        bar2 = 2,
    }

    type UnionOfBar = TypeBar1 | TypeBar2;

    type TypeBar1 = {

        type: BarEnum.bar1;
    };

    type TypeBar2 = {

        type: BarEnum.bar2;
    };

    const value: Partial<UnionOfBar> = {} as any;

    if (value.type !== undefined) {

        switch (value.type) {

            case BarEnum.bar1: break;
            case BarEnum.bar2: break;
            default: never(value.type); // Error: Type 'undefined' is not assignable to parameter of type 'never'
        }
    }

Expected behavior:
No compiler error

Actual behavior:
Type 'undefined' is not assignable to parameter of type 'never'

Playground Link:
http://www.typescriptlang.org/play/#src=declare%20function%20never(value%3A%20never)%3A%20never%3B%0D%0A%0D%0Aconst%20enum%20BarEnum%20%7B%0D%0A%20%20%20%20%20%20%20%20bar1%20%3D%201%2C%0D%0A%20%20%20%20%20%20%20%20bar2%20%3D%202%2C%0D%0A%20%20%20%20%7D%0D%0A%0D%0A%20%20%20%20type%20UnionOfBar%20%3D%20TypeBar1%20%7C%20TypeBar2%3B%0D%0A%0D%0A%20%20%20%20type%20TypeBar1%20%3D%20%7B%0D%0A%0D%0A%20%20%20%20%20%20%20%20type%3A%20BarEnum.bar1%3B%0D%0A%20%20%20%20%7D%3B%0D%0A%0D%0A%20%20%20%20type%20TypeBar2%20%3D%20%7B%0D%0A%0D%0A%20%20%20%20%20%20%20%20type%3A%20BarEnum.bar2%3B%0D%0A%20%20%20%20%7D%3B%0D%0A%0D%0A%20%20%20%20const%20value%3A%20Partial%3CUnionOfBar%3E%20%3D%20%7B%7D%20as%20any%3B%0D%0A%0D%0A%20%20%20%20if%20(value.type%20!%3D%3D%20undefined)%20%7B%0D%0A%0D%0A%20%20%20%20%20%20%20%20switch%20(value.type)%20%7B%0D%0A%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20BarEnum.bar1%3A%20break%3B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20BarEnum.bar2%3A%20break%3B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20default%3A%20never(value.type)%3B%20%2F%2F%20Error%3A%20Type%20'undefined'%20is%20not%20assignable%20to%20parameter%20of%20type%20'never'%0D%0A%20%20%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%7D

Related Issues:
#28748

@NoelAbrahams
Copy link
Author

NoelAbrahams commented Jan 20, 2019

Update: this particular version of the bug is fixed in 3.3RC. But the original bug (default: return never(value.type); in the code below) is still present.

A similar error in a slightly different context:

    type GeoType2 = GeoPoint2 | GeoRoute2;

    type GeoPoint2 = {

        type: 'Point';
        coordinates: {};
        id: number;
    };

    type GeoRoute2 = {

        type: 'LineString';
        coordinates: {}[];
        start: { id: number, name: string };
        end: { id: number, name: string };
    };

    function foo(value: Partial<GeoType2>) {

        switch (value.type) {

            case 'Point':

                return;

            case 'LineString':

                return !!value.start && !!value.end && !!value.start.id && !!value.end.id && !!value.coordinates
                    && value.start.name && value.end.name; // HERE

            default: return never(value.type);
        }
    }

The compiler reports an error for undefined access at the point marked "HERE". The start and end properties should narrow out undefined.

@dhruvrajvanshi
Copy link
Contributor

dhruvrajvanshi commented Jan 20, 2019

Your first case is correct behaviour according to the definition of Partial.

type can still be undefined because Partial<T> means each key of T can be undefined.

undefined is not assignable to never. never type has no inhabitants.

EDIT:
Here's the "fixed" version of the first example. Note that I've added a new case to your switch statement to handle the undefined case, which makes value to be correctly inferred as never.

Proof of this is

const value: Partial<UnionOfBar> = { type: undefined }

This is valid and type checks correctly

http://www.typescriptlang.org/play/#src=declare%20function%20never(value%3A%20never)%3A%20never%3B%0D%0A%0D%0Aconst%20enum%20BarEnum%20%7B%0D%0A%20%20%20%20%20%20%20%20bar1%20%3D%201%2C%0D%0A%20%20%20%20%20%20%20%20bar2%20%3D%202%2C%0D%0A%20%20%20%20%7D%0D%0A%0D%0A%20%20%20%20type%20UnionOfBar%20%3D%20TypeBar1%20%7C%20TypeBar2%3B%0D%0A%0D%0A%20%20%20%20type%20TypeBar1%20%3D%20%7B%0D%0A%0D%0A%20%20%20%20%20%20%20%20type%3A%20BarEnum.bar1%3B%0D%0A%20%20%20%20%7D%3B%0D%0A%0D%0A%20%20%20%20type%20TypeBar2%20%3D%20%7B%0D%0A%0D%0A%20%20%20%20%20%20%20%20type%3A%20BarEnum.bar2%3B%0D%0A%20%20%20%20%7D%3B%0D%0A%0D%0A%20%20%20%20const%20value%3A%20Partial%3CUnionOfBar%3E%20%3D%20%7B%7D%20as%20any%3B%0D%0A%0D%0A%20%20%20%20if%20(value.type%20!%3D%3D%20undefined)%20%7B%0D%0A%0D%0A%20%20%20%20%20%20%20%20switch%20(value.type)%20%7B%0D%0A%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20BarEnum.bar1%3A%20break%3B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20BarEnum.bar2%3A%20break%3B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20default%3A%20never(value.type)%3B%20%2F%2F%20Error%3A%20Type%20'undefined'%20is%20not%20assignable%20to%20parameter%20of%20type%20'never'%0D%0A%20%20%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%7D

@NoelAbrahams
Copy link
Author

@dhruvrajvanshi there is a check for undefined before the switch statement, so value.type cannot be undefined within the switch.

@dhruvrajvanshi
Copy link
Contributor

Welp. I missed the undefined check. It does seem like a genuine bug to me. Sorry about that.

Converting the switch case to equivalent if else statements does seem to work so I don't see why this wouldn't work.

@NoelAbrahams
Copy link
Author

The playground is actually working correctly for both reported bugs. But following the download link from the playground for Visual Studio 2017 and installing version 3.2.2.0 causes the same error.

@NoelAbrahams
Copy link
Author

I think this is fixed in TypeScript 3.2.4 but there is no download link to this version.

https://www.microsoft.com/en-us/download/details.aspx?id=55258

@DanielRosenwasser is there a download link for TS 3.2.4 so that I can test and close this issue? Thanks!

@NoelAbrahams
Copy link
Author

The bug is still present in TS 3.3RC.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jan 23, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.4.0 milestone Jan 23, 2019
@jack-williams
Copy link
Collaborator

I'm afraid I can't offer up a solution, but in terms of tracing the problem this looks to be something affected by this fix here #27612. Existing narrowings on properties, such as value.type !== undefined, are getting reset when a switch on a discriminant happens, such as switch (value.type). The example looks to be the same as the one in this comment.

This is the comment by @ahejlsberg regarding the issue:

Yes, that is due to #27612. We weren't correctly resetting our narrowing assumptions in switch statements. It happened to work in some cases, including yours, but generally was wrong and inconsistent with corresponding if statements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests

6 participants