Skip to content

False branch of in-operator type guard for non-union intersection is not never #37039

@pizzacat83

Description

@pizzacat83

TypeScript Version: Nightly

Search Terms: intersection guard, intersection never

Expected behavior: In the first function, the type of obj in the else block is never

Actual behavior: In the first function, the type of obj in the else block is { str: string } & { num: number }, the same as in the if block.

Related Issues: I didn't find any.

Code

(obj: { str: string } & { num: number }) => {
    // Non-Union Intersection type doesn't work as expected.
    if ('str' in obj) {
        const s: string = obj.str;
        // obj: { str: string } & { num: number }
    } else {
        const _exhaustiveCheck: never = obj;
        // Expected: No error. obj: never
        // Actual: Type '{ str: string; } & { num: number; }' is not assignable to type 'never'.
    }
};

(obj: { str: string; num: number }) => {
    // Non-intersection type works as expected.
    if ('str' in obj) {
        const s: string = obj.str;
        // obj: { str: string; num: number }
    } else {
        const _exhaustiveCheck: never = obj;
        // No error. obj: never
    }
};

(obj: { str: string } & { num: number } | { bool: boolean }) => {
    // Union of intersection works as expected.
    if ('str' in obj) {
        const s: string = obj.str;
        // obj: { str: string; num: number }
    } else {
        const proveIsBool: boolean = obj.bool;
        // No error. obj: { bool: boolean }
    }
};
Output
"use strict";
(obj) => {
    // Non-Union Intersection type doesn't work as expected.
    if ('str' in obj) {
        const s = obj.str;
        // obj: { str: string } & { num: number }
    }
    else {
        const _exhaustiveCheck = obj;
        // Expected: No error. obj: never
        // Actual: Type '{ str: string; } & { num: number; }' is not assignable to type 'never'.
    }
};
(obj) => {
    // Non-intersection type works as expected.
    if ('str' in obj) {
        const s = obj.str;
        // obj: { str: string; num: number }
    }
    else {
        const _exhaustiveCheck = obj;
        // No error. obj: never
    }
};
(obj) => {
    // Union of intersection works as expected.
    if ('str' in obj) {
        const s = obj.str;
        // obj: { str: string; num: number }
    }
    else {
        const proveIsBool = obj.bool;
        // No error. obj: { bool: boolean }
    }
};
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "useDefineForClassFields": false,
    "alwaysStrict": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "downlevelIteration": false,
    "noEmitHelpers": false,
    "noLib": false,
    "noStrictGenericChecks": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "esModuleInterop": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "skipLibCheck": false,
    "checkJs": false,
    "allowJs": false,
    "declaration": true,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,
    "target": "ES2017",
    "module": "ESNext"
  }
}

Playground Link: Provided

Metadata

Metadata

Assignees

Labels

Breaking ChangeWould introduce errors in existing codeBugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions