Skip to content

Inconsistent behaviour of type narrowing of discriminated union with switch-case and if statements #30763

@roberto-marquez

Description

@roberto-marquez

TypeScript Version:
3.4.1

Search Terms:
discriminated union, type guard, switch-case, switch, case, if, type narrowing

Code

const BOOLEAN: "BOOLEAN" = "BOOLEAN";
const NUMBERS: "NUMBERS" = "NUMBERS";

type ActionsUnion = { type: typeof NUMBERS; payload: { nums : number[ ] } } 
| { type: typeof BOOLEAN; payload: { bool : boolean } };

const typeGuard = (action: any): action is ActionsUnion => true;

const exampleFunction = (action: unknown) => {

  if (typeGuard(action)) {
    // type narrowing works with switch-case statement
    switch (action.type) {
      case BOOLEAN:
        action.payload.bool; // works
        break;
    }

    // type narrowing does not work with if statement
    if (action.type === BOOLEAN) action.payload.bool; // Error:
   // Property 'bool' does not exist on type '{ bool: boolean; } | { nums: number[]; }'.
   // Property 'bool' does not exist on type '{ nums: number[]; }'.

    // type narrowing does work with if statement when assigning argument to local variable
    // type inference can figure out the correct type.
    const actionVar = action;
    if (actionVar.type === BOOLEAN) actionVar.payload.bool; // works
  }
};

Expected behavior:
When checking the discriminant property of a discriminated union the type narrowing should work for switch-case statement as well as if statements.

Actual behavior:
Switch-case statements work for narrowing a discriminated union while if statements do not unless we use type inference by assigning object to variable.

Playground Link:
https://www.typescriptlang.org/play/index.html#src=const%20BOOLEAN%3A%20%22BOOLEAN%22%20%3D%20%22BOOLEAN%22%3B%0D%0Aconst%20NUMBERS%3A%20%22NUMBERS%22%20%3D%20%22NUMBERS%22%3B%0D%0A%0D%0Atype%20ActionsUnion%20%3D%20%7B%20type%3A%20typeof%20NUMBERS%3B%20payload%3A%20%7B%20nums%20%3A%20number%5B%20%5D%20%7D%20%7D%20%0D%0A%7C%20%7B%20type%3A%20typeof%20BOOLEAN%3B%20payload%3A%20%7B%20bool%20%3A%20boolean%20%7D%20%7D%3B%0D%0A%0D%0Aconst%20typeGuard%20%3D%20(action%3A%20any)%3A%20action%20is%20ActionsUnion%20%3D%3E%20true%3B%0D%0A%0D%0Aconst%20exampleFunction%20%3D%20(action%3A%20unknown)%20%3D%3E%20%7B%0D%0A%0D%0A%20%20if%20(typeGuard(action))%20%7B%0D%0A%0D%0A%20%20%20%20switch%20(action.type)%20%7B%0D%0A%20%20%20%20%20%20case%20BOOLEAN%3A%0D%0A%20%20%20%20%20%20%20%20action.payload.bool%3B%0D%0A%20%20%20%20%20%20%20%20break%3B%0D%0A%20%20%20%20%7D%0D%0A%0D%0A%20%20%20%20if%20(action.type%20%3D%3D%3D%20BOOLEAN)%20action.payload.bool%3B%0D%0A%0D%0A%20%20%20%20const%20actionVar%20%3D%20action%3B%0D%0A%20%20%20%20if%20(actionVar.type%20%3D%3D%3D%20BOOLEAN)%20actionVar.payload.bool%3B%0D%0A%20%20%7D%0D%0A%7D%3B

Related Issues:
#11787

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions