Skip to content

Discriminated union types lose their narrowed type inside nested function scopes #9258

Description

TypeScript Version:

nightly (1.9.0-dev.20160619-1.0)

Code

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Rectangle | Circle;

function test1(s: Shape) {
    if (s.kind === "rectangle") {
        s;  // Rectangle
        setTimeout(() => {
          s; // Rectangle | Circle
        });
    }
}

Expected behavior: both s usages to narrow to type Rectangle

Actual behavior: the s in the nested function scope has type Rectangle | Circle

Activity

  1. yortus commented on Jun 20, 2016

    @yortus
    Contributor

    I believe this is by design, and applies to all type guards, not just those for discriminated unions. The problem is that the compiler cannot in general be sure when the nested function will be called, so it's not safe to assume the narrowed type will still apply by the time the function is actually called. The variable may have been re-assigned by then.

    There are a couple of special cases involving IIFEs and consts where the narrowing can safely cross into the nested function body, and the compiler handles these cases correctly. See #8849.

  2. ryanatkn commented on Jun 20, 2016

    @ryanatkn
    Author

    Makes sense, thank you!

    So the workaround would look like this:

    interface Rectangle {
        kind: "rectangle";
        width: number;
        height: number;
    }
    
    interface Circle {
        kind: "circle";
        radius: number;
    }
    
    type Shape = Rectangle | Circle;
    
    function test1(s: Shape) {
        if (s.kind === "rectangle") {
            const rect = s;
            s;  // Rectangle
            setTimeout(() => {
              s; // Rectangle | Circle
              rect; // Rectangle
            });
        }
    }
  3. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions