Skip to content

Fix discriminant property narrowing through optional chain with null #42503

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

Merged
merged 3 commits into from
Jan 29, 2021

Conversation

andrewbranch
Copy link
Member

Fixes #42480

Got mixed up in #42450 thinking of type as the type of the discriminant property access, which would be undefined even if the union type included null. But type is the union, not the discriminant property, so we need to filter out both null and undefined, but then only mix back in undefined to the discriminant property type to model the behavior of an optional chain.

@DanielRosenwasser
Copy link
Member

@ahejlsberg ahejlsberg self-requested a review January 26, 2021 23:09
Copy link
Member

@ahejlsberg ahejlsberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funny, I was looking at this issue and came up with exactly the same fix. I had planned to add the following tests, I suggest you include them in this PR:

type U = { kind: undefined, u: 'u' }
type N = { kind: null, n: 'n' }
type X = { kind: 'X', x: 'x' }

function f1(x: X | U | undefined) {
    if (x?.kind === undefined) {
        x;
    }
    else {
        x;
    }
}

function f2(x: X | N | undefined) {
    if (x?.kind === undefined) {
        x;
    }
    else {
        x;
    }
}

function f3(x: X | U | null) {
    if (x?.kind === undefined) {
        x;
    }
    else {
        x;
    }
}

function f4(x: X | N | null) {
    if (x?.kind === undefined) {
        x;
    }
    else {
        x;
    }
}

function f5(x: X | U | undefined) {
    if (x?.kind === null) {
        x;
    }
    else {
        x;
    }
}

function f6(x: X | N | undefined) {
    if (x?.kind === null) {
        x;
    }
    else {
        x;
    }
}

function f7(x: X | U | null) {
    if (x?.kind === null) {
        x;
    }
    else {
        x;
    }
}

function f8(x: X | N | null) {
    if (x?.kind === null) {
        x;
    }
    else {
        x;
    }
}

if (!propType) {
return type;
}
propType = removeOptional ? getOptionalType(propType) : propType;
propType = removeNullable ? getOptionalType(propType) : propType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional chaining leverages an optionalType marker type to indicate the type of an expression came from a ?.. The optionalType marker is essentially undefined, but its presence has meaning to completions as well as within the checker itself. The code here (and from #42450), removes undefined (regardless of source), then mixes back in undefinedType. In functions like checkPropertyAccessChain, I use the function propagateOptionalTypeMarker to restore optionalType to the result if it was found in the source type to carry that marker forward.

I'm not certain yet whether this (or #42450) will cause problems as a result of not restoring the optionalType marker, as I need to consider if there is a test case that might trigger unwanted behavior.

@andrewbranch
Copy link
Member Author

@rbuckton it sounds like any potential problem is already in master, so I’m going to go ahead and merge this. Let me know if you have a suggestion for a follow-up and I’ll be happy to add to it.

@andrewbranch andrewbranch merged commit c15f40a into microsoft:master Jan 29, 2021
@andrewbranch andrewbranch deleted the bug/42480 branch January 29, 2021 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Milestone Bug PRs that fix a bug with a specific milestone
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optional chaining does not narrow types in the else branch
5 participants