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

[Feature Request] Type guard on conditional types #23493

Closed
Cryrivers opened this issue Apr 18, 2018 · 6 comments
Closed

[Feature Request] Type guard on conditional types #23493

Cryrivers opened this issue Apr 18, 2018 · 6 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@Cryrivers
Copy link

Code

type Test<V, M extends boolean> = { multiple?: M } & (
    M extends true ? { values: V[] } : { value: V }
)

function typeGuard<V, M extends boolean>(input: Test<V, M>) {
    if (input.multiple) {
        return input.values;
    } else {
        return input.value;
    }
}

Expected behavior:
input.values can be inferred in if block and input.value can be inferred in else block.

Actual behavior:
Property 'values' does not exist on type 'Test<V, M>'.
Property 'value' does not exist on type 'Test<V, M>'.

Playground Link: https://www.typescriptlang.org/play/#src=type%20Test%3CV%2C%20M%20extends%20boolean%3E%20%3D%20%7B%20multiple%3F%3A%20M%20%7D%20%26%20(%0D%0A%20%20%20%20M%20extends%20true%20%3F%20%7B%20values%3A%20V%5B%5D%20%7D%20%3A%20%7B%20value%3A%20V%20%7D%0D%0A)%0D%0A%0D%0Afunction%20typeGuard%3CV%2C%20M%20extends%20boolean%3E(input%3A%20Test%3CV%2C%20M%3E)%20%7B%0D%0A%20%20%20%20if%20(input.multiple)%20%7B%0D%0A%20%20%20%20%20%20%20%20return%20input.values%3B%0D%0A%20%20%20%20%7D%20else%20%7B%0D%0A%20%20%20%20%20%20%20%20return%20input.value%3B%0D%0A%20%20%20%20%7D%0D%0A%7D

Related Issues:

@jack-williams
Copy link
Collaborator

jack-williams commented Apr 18, 2018

I'm not sure narrowing would be sound:

let x: Test<string, boolean> = { multiple: true, value: "hello" };
typeGuard<string, boolean>(x);
/*
    Passes type-checker.
    input.multiple === true does not imply input.values is present.
*/

Why not use a union type with a discriminant property? (requires --strictNullChecks).

type Test<V> = { multiple: true; values: V[] } | { multiple?: false; value: V };

function typeGuard<V>(input: Test<V>) {
    if (input.multiple) {
        return input.values;
    } else {
        return input.value;
    }
}

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Apr 18, 2018
@Kingwl
Copy link
Contributor

Kingwl commented Apr 23, 2018

have this issue any plan?🤫

@Cryrivers
Copy link
Author

Cryrivers commented Apr 23, 2018

@Kingwl probably no plans yet as marked as Working as intended. For most cases you can use discriminant properties to solve the problem, which @jack-williams posted. My case is more complicated than his (or mine, as I posted the simplified case to demo my idea) and it is unlikely to be done with discriminant properties. I'll post a demo if I have time.

Thanks.

@Kingwl
Copy link
Contributor

Kingwl commented Apr 25, 2018

@Cryrivers
thanks for your suggestion
I mean i'd like to try this if have not any plan or owner to fix this

@kpdonn
Copy link
Contributor

kpdonn commented Apr 25, 2018

@Kingwl

I think the thing is that it can't be safely fixed due to @jack-williams example. The problem is that the types for Test can look like any of the following:

type Test<V, M extends boolean> = { multiple?: M } & (
    M extends true ? { values: V[] } : { value: V }
)

type TestTrue = Test<string, true> // { multiple?: true; } & { values: string[]; }
type TestFalse = Test<string, false> // { multiple?: false; } & { value: string; }
type TestBoolean = Test<string, boolean>
// ({ multiple?: boolean; } & { values: string[]; }) | ({ multiple?: boolean; } & { value: string; })

It's the third case that makes

let x: Test<string, boolean> = { multiple: true, value: "hello" };

a valid assignment and would make the proposed narrowing unsound.

Although as a side note you can force Test<string, boolean> to be a compile error by doing:

type XorBoolean<B extends boolean> = boolean extends B ? never : boolean
type Test2<V, M extends XorBoolean<M>> = { multiple?: M } & (
    M extends true ? { values: V[] } : { value: V }
)
type Test2True = Test2<string, true> // { multiple?: true; } & { values: string[]; }
type Test2False = Test2<string, false> // { multiple?: false; } & { value: string; }
type Test2Boolean = Test2<string, boolean> // error: type boolean does not satisfy constraint never

Still doesn't narrow in the original example however since type constraints like that aren't taken into account.

Playground link of examples

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

6 participants