Skip to content
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

Using undefined as a discriminator within a mapped type yields erroneous types when primitive intersections are involved #14471

Open
weswigham opened this issue Mar 5, 2017 · 2 comments
Labels
Needs Investigation This issue needs a team member to investigate its status.
Milestone

Comments

@weswigham
Copy link
Member

TypeScript Version: 2.2.0

Code

// A *self-contained* demonstration of the problem follows...
type DeepReadonly<T> = {
    readonly [K in keyof T]: DeepReadonly<T[K]>;
}

interface FieldBrand {
    " do not use ": void;
}

type FieldId = number & FieldBrand;

interface DefOne {
    field: string | FieldId;
    kind: string;
}

interface DefTwo {
    field?: undefined; // Allow discriminant checks on 'field'
    value: string;
}

type Def = DefOne | DefTwo;

interface State {
    a?: Def;
    b?: Def;
}

type ROState = DeepReadonly<State>;

function lookupName(f: FieldId): string {
    return "";
}

function remapFieldsToNames(channels: ROState): ROState {
    const newState: State = {};
    for (const k of Object.keys(channels)) {
        const key = k as keyof ROState;
        const ch = channels[key];
        let replacement: ROState[typeof key] | undefined = undefined;
        if (ch) {
            if (ch.field) {
                const f = ch.field;
                if (typeof f === "number") {
                    f; // Should be FieldId or number, not never!
                    replacement = { ...ch, field: lookupName(f) };
                }
                else if (typeof f === "string") {
                    f; // correct
                }
            }
        }
        newState[k] = replacement || channels[k];
    }
    return newState;
}

Expected behavior:
There are no type errors in the above, and f after the typeof f === "number" check is either a number or FieldId.

Actual behavior:
replacement = { ...ch, field: lookupName(f) }; has a type error and f is of type never.

@weswigham
Copy link
Member Author

There's also some odd shenanigans going on with DeepReadonly leaving off its generic arguments in the quickinfo within the function body which may be related/should be looked into:
image

@weswigham
Copy link
Member Author

The weirdness with DeepReadonly and a tagged number actually cause a type incombatibility every time I try to assign the deeply readonly structure to the normal one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

2 participants