Skip to content

Discriminated union pattern fails to narrow type inside user-defined typeguards #13962

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

Closed
dlants opened this issue Feb 8, 2017 · 1 comment
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@dlants
Copy link

dlants commented Feb 8, 2017

TypeScript Version: 2.1.5
Also works on typescript playground

Code

type typedActions =
    { type: "action1", a:string }
    | { type: "action2", b:string }

let typeGuard = function (action): action is typedActions {
    return true
}

let broken = function(action) {
    if (typeGuard(action)) {
        // we typeguard action to typedActions
        switch (action.type) {
            case "action1":
                // we didn't typeguard down to action1 :(
                console.log(action.a)
                break
            case "action2":
                console.log(action.b)
                break
        }
    }
}

let works = function(action) {
    if (typeGuard(action)) {
        // what is this doing?
        let myAction = action
        switch (myAction.type) {
            case "action1":
                // discriminated union pattern works!
                console.log(myAction.a)
                break
            case "action2":
                console.log(myAction.b)
                break
        }
    }
}

http://www.typescriptlang.org/play/index.html#src=type%20typedActions%20%3D%0D%0A%20%20%20%20%7B%20type%3A%20%22action1%22%2C%20a%3Astring%20%7D%0D%0A%20%20%20%20%7C%20%7B%20type%3A%20%22action2%22%2C%20b%3Astring%20%7D%0D%0A%0D%0Alet%20typeGuard%20%3D%20function%20(action)%3A%20action%20is%20typedActions%20%7B%0D%0A%20%20%20%20return%20true%0D%0A%7D%0D%0A%0D%0Alet%20broken%20%3D%20function(action)%20%7B%0D%0A%20%20%20%20if%20(typeGuard(action))%20%7B%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20we%20typeguard%20action%20to%20typedActions%0D%0A%20%20%20%20%20%20%20%20switch%20(action.type)%20%7B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%22action1%22%3A%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20we%20didn't%20typeguard%20down%20to%20action1%20%3A(%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20console.log(action.a)%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%22action2%22%3A%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20console.log(action.b)%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0D%0A%20%20%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Alet%20works%20%3D%20function(action)%20%7B%0D%0A%20%20%20%20if%20(typeGuard(action))%20%7B%0D%0A%20%20%20%20%20%20%20%20%2F%2F%20what%20is%20this%20doing%3F%0D%0A%20%20%20%20%20%20%20%20let%20myAction%20%3D%20action%0D%0A%20%20%20%20%20%20%20%20switch%20(myAction.type)%20%7B%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%22action1%22%3A%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20discriminated%20union%20pattern%20works!%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20console.log(myAction.a)%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%22action2%22%3A%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20console.log(myAction.b)%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0D%0A%20%20%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%7D%0D%0A%7D

Expected behavior:
When action is typeguarded to typedActions using the typeGuard function, we should be able to do a discriminated union over type, meaning that accessing action.a inside the case statement should work.

Somehow, simply copying over let myAction = action inside of the typeguard makes the code work (see second function).

Actual behavior:
There is a type error that action doesn't have property a.

@ahejlsberg
Copy link
Member

This is a design limitation in the control flow analyzer. We only narrow discriminated union types when the declared type of a variable is a union type. In this case, the declared type of action is any (because it has no type annotation) and therefore we don't narrow it. When you introduce a temporary variable, we infer the declared type of myAction to be typedActions and thus discriminated union narrowing works.

It would be nice not to have this limitation, but it would require more dependency reasoning in the control flow analyzer than we currently do.

@ahejlsberg ahejlsberg added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Feb 10, 2017
@mhegazy mhegazy closed this as completed Apr 24, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

3 participants