TypeScript type guard doesn't realize string union has been reduced

In the following code, I expect that TypeScript should be able to realize that at the line with the comment, the user object cannot have any other value for role expect for admin.

And it kinda does! If you paste this code into TypeScript Playground, you will notice that hovering over role at that point does indeed show just admin!

But still, an error is shown at the return DemoForAdmin line. And hovering over just user there, the popup showing the type shows the full union for the role field.

function Demo(user: { name: string; role: 'user' | 'admin'; } | null) {
    if (user === null) {
        return 'You have to be logged in.';
    }

    if (user.role !== 'admin') {
        return 'You have to be an admin';
    }

    user.role; // Hover over role to see "admin"
    return DemoForAdmin(user);
}

function DemoForAdmin(user: { name: string; role: 'admin'; }) {
    return 'You are an admin';
}

Shouldn't TypeScript be able to realize it is safe (it is, right?) to pass the object to that method and should it be able to infer the correct narrowed string union in both cases of hovering over user as well as role, not just role?

Upvotes: 1

Views: 113

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250056

The type guard only applies to the field and only narrows the field, the type guard will not impact the containing object type.

There is another construct that allows you to do this called discriminated union. The type has to be a union of object types with a discriminat of a litearal type. In this case role can be used. This should work

function Demo(user: { name: string; role: 'user' } | { name: string; role: 'admin'; } | null) {
    if (user === null) {
        return 'You have to be logged in.';
    }

    if (user.role !== 'admin') {
        return 'You have to be an admin';
    }

    user.role; // Hover over role to see "admin"
    return DemoForAdmin(user);
}

function DemoForAdmin(user: { name: string; role: 'admin'; }) {
    return 'You are an admin';
}

You can read more about discriminated unions here

Upvotes: 2

Related Questions