Skip to content

Improve checking of in operator#50666

Merged
ahejlsberg merged 17 commits intomainfrom
fix21732
Sep 19, 2022
Merged

Improve checking of in operator#50666
ahejlsberg merged 17 commits intomainfrom
fix21732

Conversation

@ahejlsberg
Copy link
Copy Markdown
Member

@ahejlsberg ahejlsberg commented Sep 7, 2022

This PR improves checking of the in operator:

  • In the expression key in obj, we now require the type of key to be assignable to string | number | symbol. Previously we allowed key to be of an unconstrained type parameter type.
  • In the expression key in obj, we now require the type of obj to be assignable to object. Previously we allowed obj to be of an unconstrained generic type (which might be a primitive type, causing an exception to be thrown at runtime).
  • In control flow analysis, an expression key in obj, where key is of a string literal, numeric literal, or unique symbol type, narrows obj as follows:
    • If key names a property that exists in some constituent of the type of obj, the type of obj is narrowed based on the presence or absence of that property. Previously we only did this for string literal keys.
    • If key names a property that doesn't exist in any constituent of the type of obj, the type of obj is narrowed by intersecting with Record<typeof key, unknown>. Previously we did nothing in this case.

Some examples:

const sym = Symbol();

function f1(x: unknown) {
    if (x && typeof x === "object" && "a" in x && 1 in x && sym in x) {
        x;  // Record<"a", unknown> & Record<1, unknown> & Record<typeof sym, unknown>
        x.a;  // Ok
        x[1];  // Ok
        x[sym];  // Ok
    }
}

function f2(x: { a: string } | { b: string }) {
    if ("a" in x) {
        x;  // { a: string }
    }
    else if ("b" in x) {
        x;  // { b: string }
    }
    else {
        x;  // never
    }
}

I'm marking this PR as fixing #21732 even though it doesn't address the case of key in obj where key is of some generic type. In general, when key is of some non-finite type (like string), the only effect we can meaningfully reflect following a key in obj check is that obj[key] should be a valid expression (of type unknown), provided key and obj are both known not to have been modified since the check. This might be possible to do in the true branch of an if statement or a ternary operator, but it isn't possible in all control flow scenarios.

This PR is a breaking change because of the more accurate checking of the in operands and the more consistent narrowing of the right hand operand in control flow analysis.

Fixes #21732.
Fixes #50639.

Loading
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Author: Team Breaking Change Would introduce errors in existing code For Backlog Bug PRs that fix a backlog bug 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.

in operator on T & object narrows to never Suggestion: treat in operator as type guard which asserts property existence