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

Suggestion: typeof arg in conditional type uses typeof passed value, rather than declared type of parameter #22984

Closed
krryan opened this issue Mar 29, 2018 · 6 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@krryan
Copy link

krryan commented Mar 29, 2018

A workaround I attempted for #21879 was to use typeof on a function argument in a conditional type, to indicate which callback parameters were relevant. The code was this:

interface Foo { kind: 'foo'; }
interface Bar { kind: 'bar'; }

function mapFooOrBar<R>(
    foobar: Foo | Bar,
    mapFoo: typeof foobar extends Foo ? ((value: Foo) => R) : ((impossible: never) => never),
    mapBar: typeof foobar extends Bar ? ((value: Bar) => R) : ((impossible: never) => never),
): R {

With the idea being that if I call mapFooOrBar(foo, for foo: Foo, I should expect that mapBar will never be called, and thus never have a parameter and never have a return value.

But the problem is that typeof foobar extends Bar is always true, because typeof foobar evaluates to Foo | Bar—its declared type. It would be better if typeof here used the type of the actual passed value, rather than the function parameter declaration type.

Using a generic here does not work, because for example you could write mapFooOrBar<Foo, {}>(foobar, for foobar: Foo & Bar (nevermind that Foo & Bar is impossible for these definitions of Foo and Bar, TS allows it), allowing the user to claim no Bar will be passed while actually passing a value that is a Bar.

For backwards compatibility/corner-cases, a new keyword might be better than typeof. I don't currently have a good suggestion for what that would be, though.

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Mar 29, 2018
@mhegazy
Copy link
Contributor

mhegazy commented Mar 29, 2018

typeof operator evaluates immediately, so typeof foobar is literally telling the compiler, go get the type annotation on foobar and put it here, which is Foo | Bar.
What you want here is a generic type parameter:

function mapFooOrBar<T extends Foo | Bar, R>(
    foobar: T,
    mapFoo: T extends Foo ? ((value: Foo) => R) : ((impossible: never) => never),
    mapBar: T extends Bar ? ((value: Bar) => R) : ((impossible: never) => never),
): R 

@krryan
Copy link
Author

krryan commented Mar 29, 2018

@mhegazy I understand what it is doing; my suggestion was that it not do that. Or that something that does not do that be added.

I also addressed already why a generic parameter is not workable in this situation. What I need is a way to refer to exactly what typeof would produce on the argument passed in, which may not match the type passed to the generic argument (in the explicit case) or the type inferred for the generic parameter (in the implicit case).

See also the linked #21879 for why your suggestion doesn't work, and why making it work has already been rejected.

Personally, I would have thought it would be better/more consistent to recognize Foo & Bar as impossible and thus that the problematic case described in #21879 isn't actually a problem for the given types, but discussion in #14094 (here) that it is quite intentional to allow Foo & Bar to be treated as a possibility.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 29, 2018

I also addressed already why a generic parameter is not workable in this situation. What I need is a way to refer to exactly what typeof would produce on the argument passed in, which may not match the type passed to the generic argument (in the explicit case) or the type inferred for the generic parameter (in the implicit case).

not sure i understand what you mean.

See also the linked #21879 for why your suggestion doesn't work, and why making it work has already been rejected.

typeof as an operator has a clear and a well defined behavior, not sure why it is related to this issue..

@krryan
Copy link
Author

krryan commented Mar 29, 2018

#21879 does not work because of the generic parameter, and the possibility that the generic parameter does not precisely match the type of foobar.

I am therefore looking for alternatives.

Some way of referencing the actual type of foobar would be such an alternative. This suggestion is that either typeof could be extended to have this function in this use-case, or else another keyword similar to typeof but effectively delaying that evaluation could be a solution.

@mhegazy
Copy link
Contributor

mhegazy commented Apr 4, 2018

I am therefore looking for alternatives.

i understand, but i do not think typeof is a viable option here. #21879 is rather a design limitation in the current system.. I have filed #23132 to see if there is anythign else we can do here.

@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 30, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants