You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Search Terms:
Type narrowing void
Optional Chaining void
Code
typeA={b: string};// example of how I try to use it:async(p: Promise<A>)=>{consta=awaitp.catch(console.error);a?.b;// error Property 'b' does not exist on type 'void | A'.};// simplified examples:(a: A|void)=>{a?.b;// error Property 'b' does not exist on type 'void | A'.a ? a.b : undefined// works};(a: A|undefined)=>{a?.b;// works};(a: A|null)=>{a?.b;// works};
Expected behavior:
No errors when using optional chaining with a T | void if T contains the fields specified. I guess void could be represented as a value as undefined considering that a function that returns undefined returns type void.
Actually another question if according to #26262, void shouldn't allow checking for truthiness
Why is it allowed here when it's a discriminated union with another type?
async(p: Promise<A>)=>{consta=awaitp.catch(console.error);// do stuff with a};
Apparently a will have type A | void. Passing any function that returns void to catch will yield the same type which might cause unwanted behaviors if checked for truthiness and the function returns something other than undefined.
I know this issue doesn't track this and it's good to close it. But what do you think about such a use case?
There are also a lot of confusion between void and undefined in general.
For instance in react-apollo the result of a mutation was typed with void | {data: Data}.
constresponse=mutation();if(response){// This is fine because if(void|{data: Data}) removed void from unionconstdata=response.data}// This is not fine because ?. won't remove void thus giving error that "data" doesn't exist on voidconstdata=response?.data;
While I agree that void and undefined are different, in practice there are used interchangeably.
If you stick to the argument that void | A is not legitimate to do, why are we allowed to write it?
Either void is special and can't be union'ed with another type or it behaves like undefined and should be removed from the union when using ?.
Reacted by Mathieu M-Gosselin, Phil Perron and Feli
The category of "things which you should not do because they are ultimately incoherent" is unbounded; it's similarly useless to write 0 * ((x + a + b) * 1 + 3) but not a categorical error. void has a meaning and | has a meaning; if you misapprehend those meanings and combine them you'll get weird results.
Banning of certain combinations of type operators and type operands has been discussed extensively before; in a language with structural generics it's ultimately a fool's errand because you can't prevent generic instantiations that end up creating "illegal" types.
You make a very good point why void | A should not be blocked and I totally agree with you there.
My main question is why was it determined that if (void | A) will produce A in the if block and can we apply the same logic for ?.?
We're actually thinking that the if block shouldn't narrow, since some other truthy value might inhabit response. If mutation is guaranteed to return undefined when it doesn't produce the prescribed value, then it should be written as such rather than using void
The biggest problem with making such a change is all the libraries incorrectly using void instead of undefined would become broken. Upgrading is not always easy or possible in the short time for various reasons.
We'd be stuck having to do something like
typeReplaceVoid<T>=Textendsvoid ? Exclude<T,void>|undefined : T;functionremoveVoid<T>(a: T): ReplaceVoid<T>{returnaasany;}typeA=void|{data: any};functionfoo(a: A){// for if blockconst_a=removeVoid(a);if(_a){_a.data}// using && operator_a&&_a.data;// using optional chainingremoveVoid(a)?.data;}
Maybe there is an easier way to convert void to undefined, but we'd definitely need it if Typescript is going in that direction.
Activity
nmain commentedon Nov 20, 2019
undefinedandvoidare not synonymous.voidrepresents that the return of a function could be anything and should not be used for anything, and so has consequences like https://github.com/microsoft/TypeScript/wiki/FAQ#why-are-functions-returning-non-void-assignable-to-function-returning-voidSo your request can be broken with something like this:
sharno commentedon Nov 20, 2019
Nicholai Main (@nmain) Makes sense, thanks for the clarification
sharno commentedon Nov 20, 2019
Actually another question if according to #26262,
voidshouldn't allow checking for truthinessWhy is it allowed here when it's a discriminated union with another type?
Do you think I should file another bug for this?
nmain commentedon Nov 20, 2019
Good question. I'm guessing that was intentional, but it does seem inconsistent;
T | voidcan be tested for truthiness, but not safe-navigated. 🤷♂RyanCavanaugh commentedon Nov 22, 2019
Testing
voidfor truthiness is a one-off rule. There's no legitimate reason to write(a: A | void) =>so whatever behavior that produces is immaterialsharno commentedon Nov 22, 2019
Ryan Cavanaugh (@RyanCavanaugh)
There's a legitimate reason I have in my code to write something like this:
Apparently
awill have typeA | void. Passing any function that returnsvoidtocatchwill yield the same type which might cause unwanted behaviors if checked for truthiness and the function returns something other thanundefined.I know this issue doesn't track this and it's good to close it. But what do you think about such a use case?
Cellule commentedon Nov 27, 2019
There are also a lot of confusion between
voidandundefinedin general.For instance in
react-apollothe result of a mutation was typed withvoid | {data: Data}.While I agree that
voidandundefinedare different, in practice there are used interchangeably.If you stick to the argument that
void | Ais not legitimate to do, why are we allowed to write it?Either
voidis special and can't be union'ed with another type or it behaves likeundefinedand should be removed from the union when using?.RyanCavanaugh commentedon Nov 27, 2019
The category of "things which you should not do because they are ultimately incoherent" is unbounded; it's similarly useless to write
0 * ((x + a + b) * 1 + 3)but not a categorical error.voidhas a meaning and|has a meaning; if you misapprehend those meanings and combine them you'll get weird results.Banning of certain combinations of type operators and type operands has been discussed extensively before; in a language with structural generics it's ultimately a fool's errand because you can't prevent generic instantiations that end up creating "illegal" types.
Cellule commentedon Nov 27, 2019
You make a very good point why
void | Ashould not be blocked and I totally agree with you there.My main question is why was it determined that
if (void | A)will produceAin the if block and can we apply the same logic for?.?RyanCavanaugh commentedon Nov 27, 2019
We're actually thinking that the
ifblock shouldn't narrow, since some other truthy value might inhabitresponse. Ifmutationis guaranteed to returnundefinedwhen it doesn't produce the prescribed value, then it should be written as such rather than usingvoidRyanCavanaugh commentedon Nov 27, 2019
Oh, and that
console.errorand its friends should have their return types changed fromvoidtoundefinedsince that behavior is specifiedCellule commentedon Nov 27, 2019
The biggest problem with making such a change is all the libraries incorrectly using
voidinstead of undefined would become broken. Upgrading is not always easy or possible in the short time for various reasons.We'd be stuck having to do something like
Maybe there is an easier way to convert void to undefined, but we'd definitely need it if Typescript is going in that direction.