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
{{ message }}
Repository navigation
Breaking change: Use of 'void' in control flow constructs is now disallowed #26262
Expected behavior: The "Cool!" branch is actually unreachable because existsAndIsCool returns void, not boolean. I should have been warned about this at some point.
Hi, I just updated my CI/CD pipeline env to 3.1.1 and I encounter this error while building docker image. An expression of type 'void' cannot be tested for truthiness
My code is:
I think that using the expression, that evaluates to void, like this is perfectly legit, because as typescript docs says "Declaring variables of type void is not useful because you can only assign undefined or null to them". So if it is allowed to check both, null and undefined, in this kind of expression, also void must be permitted.
In addition to that, JS's undefined value is same as void, since JS adds anyway a "return undefined" if a functions hasn't any return value.
I think that TS here forgets that it is a JS superset and this makes me sad.
Reacted by Juan Pablo de la Torre, Gilbert, Ben Steward, Qwerty (Vítězslav Ackermann Ferko), Andrej Bartko, alex a, Dovid Gefen, Valen Cheung, Stav Alfi, Zuo Zongyuan and 5 more
Reacted by Juan Pablo de la Torre, Robert Starbuck, Trotyl Yu, BlackGlory, Dan Mercer and snarbles2
Reacted by Qwerty (Vítězslav Ackermann Ferko), Andrej Bartko, Kaat, Stav Alfi, Scott Burton, Adam Matheson, Yury Goltsman, Fidanze and jwalkerinterpres
Ryan Cavanaugh (@RyanCavanaugh) I think that there is no need to assuming anything because in JS we have truthy and falsy values and all operators check for that. In addition to that conditional operators are short-circuited and return the value and don't operate any conversion type to boolean.
I assume, because of docs I linked, that also TS would work the same. If we "introduce real" true and false checks (I mean the boolean one and not truthy and falsy), also
Can't use comma operator here, but || is legit. I use TS to have a better FP experience in JS, but this type of changes, moreover in minor version, are really annoying.
Anyway, just to point it out, I am here to understand and I am not blaming anyone.
Good point about me being sad about wrongly understand ts is js's "superset". :)
Reacted by Juan Pablo de la Torre, Gilbert, Jan Starzak, Qwerty (Vítězslav Ackermann Ferko), Andrej Bartko, Romario Raffington, Kaat, Valen Cheung, Stav Alfi, Richard Richardson and 1 more
If not, what makes this type different from all other types?
Giving different behavior to different types is why we have types in the first place.
The odds that you meant to write code like the sample in the OP are approximately 0%. Even if you intended to write code like voidReturningFunc() || someImportantOp(), it's unlikely you fully understood the implication of void - many people believe that a void-returning function will always return a falsy value, but this is not the case.
Moreover, code like someCall() || someImportantOp() is an extreme code smell because it is not obvious to a reader of the code that the evaluation of someImportantOp is intended to actually be unconditional -- the comma operator should have been used here for clarity and certainty.
Reacted by AnyhowStep, S. B. Tam, Trotyl Yu, Dan Mercer and snarbles2
Reacted by Andrej Bartko, Stav Alfi, Yang and Adam Matheson
Giving different behavior to different types is why we have types in the first place.
Ah sorry, I thought it was obvious from context that I meant what makes void being coerced to a boolean value invalid typescript, as opposed to a string being coerced to boolean, or float or any other type.
From a purity standpoint, it doesn't make sense to coerce a string to boolean either, right? The only false string is '' and it seems more correct to use === to test that. So if you all are ratcheting up the purity of coercion in TypeScript, I don't think it's unreasonable to ask if you all are planning on making boolean coercion illegal for other types.
Kit Westneat (@kitwestneat) just get the point I was trying to understand here. In this very moment there are 2 different behaviour of the language for the same operation, and it is wrong from every point of view. In addition can you Ryan Cavanaugh (@RyanCavanaugh) pleas elaborate "many people believe that a void-returning function will always return a falsy value, but this is not the case"?
Also the TS docs say: "Declaring variables of type void is not useful because you can only assign undefined or null to them". So it is legit to expect to use void here, since it is the "same" as undefined and null...
It's legal to coerce a string to a boolean because a) this is idiomatic in JS, b) some strings are falsy and some strings are truthy according to rules which are more or less predictable, and c) it's easily conceivable why you might want to do some things with truthy strings and other things with falsy strings
How do you get an expression of type void? Effectively there are two ways:
Evaluating a non-aliased function call, which means it always returns undefined
In which case you're writing code that appears to have control flow but doesn't, which is extremely suspect
Evaluating an aliased function call, which means its return value is truly unknowable
In which case you have no idea what's going to happen, nor does anyone else reading the code
So it is legit to expect to use void here, since it is the "same" as undefined and null...
I think this has been clearly explained already: #26262 (comment)
functionmyForEach<T>(arr: T[],callback: (arg: T)=>void){leti=0;// love too sequence with ||while(i<arr.length)callback(arr[i])||i++;}// YepmyForEach([1,2,3],n=>console.log(n));// Let's copy this array into another arrayletdst: number[]=[];// Out of memory error, wtf?myForEach([1,2,3],n=>dst.push(n));
Reacted by Qwerty (Vítězslav Ackermann Ferko) and mrs-umg
Ok, this is really a nice example! Love it.
Don't you think that the real problem is before || operation. What is now bothering me is: n => dst.push(n) has (n: any) => number type and the callback parameter is declared as callback: (arg: T) => void). Isn't this already a problem on its own that I am allowed to pass a callback with wrong signature?
So, wrapping up all of the great comments here, I think we can say that void is meant to be used for functions that only have side-effects and that is different both from any and undefined, even if const bar: void = undefined is valid syntax.
There is still one question left though. As i pointed out before, official TS docs say "Declaring variables of type void is not useful because you can only assign undefined or null to them", but trying to write const foo: void = null gives Type 'null' is not assignable to type 'void'. error. Docs update needed?
Reacted by Chris Harvey, Juan Pablo de la Torre and Qwerty (Vítězslav Ackermann Ferko)
You have this error because you have strictNullChecks: true (or strict: true for that matter) in your tsconfig.json. By default strictNullChecks is false, and void is assignable from null.
Btw, I've got a nice type compatibility table. Paste it into any editor capable of highlighting TypeScript errors and you will see which types are compatible and which are not (depending on the active tsconfig.json).
// Type compatibility tablefunctionfx(): never{throwError("Never returns");}functionfv(): void{}varx: never=fx();varu: undefined=undefined;varv: void=fv();varn: null=null;vart: {}={};// random typevara: any={};/* |x |--+-----+y |x = y| (typeof X "assignable from" typeof Y)--+-----+*//* | never | undefined | void | null | T | any |-----------+--------------+--------------+--------------+--------------+--------------+--------------+never |*/x=x;/*|*/u=x;/*|*/v=x;/*|*/n=x;/*|*/t=x;/*|*/a=x;/*|undefined |*/x=u;/*|*/u=u;/*|*/v=u;/*|*/n=u;/*|*/t=u;/*|*/a=u;/*|void |*/x=v;/*|*/u=v;/*|*/v=v;/*|*/n=v;/*|*/t=v;/*|*/a=v;/*|null |*/x=n;/*|*/u=n;/*|*/v=n;/*|*/n=n;/*|*/t=n;/*|*/a=n;/*|T |*/x=t;/*|*/u=t;/*|*/v=t;/*|*/n=t;/*|*/t=t;/*|*/a=t;/*|any |*/x=a;/*|*/u=a;/*|*/v=a;/*|*/n=a;/*|*/t=a;/*|*/a=a;/*|-----------+--------------+--------------+--------------+--------------+--------------+--------------+*/// Observations (under `strict: true`)://// 1. `never` assignable-from only `never`// 2. `never` assignable-to any type// 3. `any` assignable-from any type// 4. `any` assignable-to any type (except for `never`, see p.1)// 5. `undefined` assignable-to `void`// 6. `undefined`, `null`, `T` are invariant//
Reacted by Ryan Cavanaugh, Paul Irish, Brendan Kenny, Connor Clark, Ilie Ciotir, Donald Pipowitch, Steven, Jordi Oliveras Rovira, Martin Hochel, OliverGrack and 13 more
Reacted by Ryan Cavanaugh, Mohsen Azimi, Giovanni Gonzaga, Jordi Oliveras Rovira, Kitson Kelly, Rudi Kovač, Harryson Palheta, Qwerty (Vítězslav Ackermann Ferko), Joey Wunderlich, cdock1029 and 2 more
// Type compatibility table
function fx(): never { throw Error("Never returns"); }
function fv(): void { }
var x: never = fx();
var u: undefined = undefined;
var v: void = fv();
var n: null = null;
var t: {} = {}; // random type
var a: any = {};
var k: unknown = {};
/*
|x |
--+-----+
y |x = y| (typeof X "assignable from" typeof Y)
--+-----+
*/
/* | never | undefined | void | null | T | any | unknown |
-----------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
never |*/ x = x; /*|*/ u = x; /*|*/ v = x; /*|*/ n = x; /*|*/ t = x; /*|*/ a = x; /*|*/ k = x; /*|
undefined |*/ x = u; /*|*/ u = u; /*|*/ v = u; /*|*/ n = u; /*|*/ t = u; /*|*/ a = u; /*|*/ k = u; /*|
void |*/ x = v; /*|*/ u = v; /*|*/ v = v; /*|*/ n = v; /*|*/ t = v; /*|*/ a = v; /*|*/ k = v; /*|
null |*/ x = n; /*|*/ u = n; /*|*/ v = n; /*|*/ n = n; /*|*/ t = n; /*|*/ a = n; /*|*/ k = n; /*|
T |*/ x = t; /*|*/ u = t; /*|*/ v = t; /*|*/ n = t; /*|*/ t = t; /*|*/ a = t; /*|*/ k = t; /*|
any |*/ x = a; /*|*/ u = a; /*|*/ v = a; /*|*/ n = a; /*|*/ t = a; /*|*/ a = a; /*|*/ k = a; /*|
k |*/ x = k; /*|*/ u = k; /*|*/ v = k; /*|*/ n = k; /*|*/ t = k; /*|*/ a = k; /*|*/ k = k; /*|
-----------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
*/
// Observations (under `strict: true`):
//
// 1. `never` assignable-from only `never`
// 2. `never` assignable-to any type
// 3. `any` assignable-from any type
// 4. `any` assignable-to any type (except for `never`, see p.1)
// 5. `undefined` assignable-to `void`
// 6. `undefined`, `null`, `T` are invariant
//
Reacted by earshinov, Martynas Žilinskas, Terry, Jordi Oliveras Rovira, Tiago Moraes, Qwerty (Vítězslav Ackermann Ferko) and Drew Showalter
Here's how it affects my project. I use swagger plugin (swagger-codegen-maven-plugin) in JAVA Spring project to generate the Angular/TS API (i.e. front-end client services and interfaces/modules). Now there's a manual step of fixing the autogenerated code, as the Angular project doesn't build due to this breaking change in TS (Use of 'void' in control flow constructs is now disallowed Error).
So, is TS going to fix this (I tested in Chrome Console JS works just fine with void evaluating to falsy) OR it is going to be a deviation from JS from now on in respect to this particular functionality ?
P.S. I also tested manual generators available, but all work probably with the same template and cannot escape the error in the generated code unless manual fixing it ... Thanks if s.o. pays attn to this
Reacted by Jeremy Moritz
Reacted by Jeremy Moritz and Qwerty (Vítězslav Ackermann Ferko)
I was using the console.debug(props) || for quickly debugging just about everything, I even have a snippet for that. With the comma separator, it is necessary to also enclose the code within brackets.
Looks like you can still coerce a void function to a number, and then do a truthiness check, so +console.log("log") || aFn() works to avoid the outer brackets needed with the comma operator.
void is a bad type that predates unknown and undefined and null and how it works is by and large an artifact of how it used to work (ie, we generally don't try to change how void behaves - we change how other things behave in the presence of void instead). If you're writing new type definitions today, it's almost always better to declare things of type unknown or undefined, depending on your intent, instead of void (which sort of acts like either, depending on context, which is why it's so inconsistent).
Activity
RyanCavanaugh commentedon Aug 7, 2018
This is now an error.
fox1t commentedon Oct 1, 2018
Hi, I just updated my CI/CD pipeline env to 3.1.1 and I encounter this error while building docker image.
An expression of type 'void' cannot be tested for truthinessMy code is:
I think that using the expression, that evaluates to void, like this is perfectly legit, because as typescript docs says "Declaring variables of type void is not useful because you can only assign undefined or null to them". So if it is allowed to check both, null and undefined, in this kind of expression, also void must be permitted.
In addition to that, JS's undefined value is same as void, since JS adds anyway a "return undefined" if a functions hasn't any return value.
I think that TS here forgets that it is a JS superset and this makes me sad.
RyanCavanaugh commentedon Oct 1, 2018
Maksim Sinik (@fox1t) the problem with assuming a void-returning function is going to return a falsy value is that this code is legal:
You should use the comma operator if you intend for two sequenced operations to always occur.
https://stackoverflow.com/questions/41750390/what-does-all-legal-javascript-is-legal-typescript-mean
fox1t commentedon Oct 1, 2018
Ryan Cavanaugh (@RyanCavanaugh) I think that there is no need to assuming anything because in JS we have truthy and falsy values and all operators check for that. In addition to that conditional operators are short-circuited and return the value and don't operate any conversion type to boolean.
I assume, because of docs I linked, that also TS would work the same. If we "introduce real" true and false checks (I mean the boolean one and not truthy and falsy), also
can be considered "illegal" because neither
barornameare not boolean values.The following isn't legal code:
Can't use comma operator here, but || is legit. I use TS to have a better FP experience in JS, but this type of changes, moreover in minor version, are really annoying.
Anyway, just to point it out, I am here to understand and I am not blaming anyone.
Good point about me being sad about wrongly understand ts is js's "superset". :)
kitwestneat commentedon Oct 4, 2018
Does this change mean that Typescript insteads to remove boolean coercion all together? If not, what makes this type different from all other types?
RyanCavanaugh commentedon Oct 4, 2018
Giving different behavior to different types is why we have types in the first place.
The odds that you meant to write code like the sample in the OP are approximately 0%. Even if you intended to write code like
voidReturningFunc() || someImportantOp(), it's unlikely you fully understood the implication ofvoid- many people believe that avoid-returning function will always return a falsy value, but this is not the case.Moreover, code like
someCall() || someImportantOp()is an extreme code smell because it is not obvious to a reader of the code that the evaluation ofsomeImportantOpis intended to actually be unconditional -- the comma operator should have been used here for clarity and certainty.kitwestneat commentedon Oct 4, 2018
Ah sorry, I thought it was obvious from context that I meant what makes void being coerced to a boolean value invalid typescript, as opposed to a string being coerced to boolean, or float or any other type.
From a purity standpoint, it doesn't make sense to coerce a string to boolean either, right? The only false string is '' and it seems more correct to use === to test that. So if you all are ratcheting up the purity of coercion in TypeScript, I don't think it's unreasonable to ask if you all are planning on making boolean coercion illegal for other types.
fox1t commentedon Oct 4, 2018
Kit Westneat (@kitwestneat) just get the point I was trying to understand here. In this very moment there are 2 different behaviour of the language for the same operation, and it is wrong from every point of view. In addition can you Ryan Cavanaugh (@RyanCavanaugh) pleas elaborate "many people believe that a void-returning function will always return a falsy value, but this is not the case"?
Also the TS docs say: "Declaring variables of type void is not useful because you can only assign undefined or null to them". So it is legit to expect to use void here, since it is the "same" as undefined and null...
RyanCavanaugh commentedon Oct 4, 2018
It's legal to coerce a string to a boolean because a) this is idiomatic in JS, b) some strings are falsy and some strings are truthy according to rules which are more or less predictable, and c) it's easily conceivable why you might want to do some things with truthy strings and other things with falsy strings
How do you get an expression of type
void? Effectively there are two ways:undefinedI think this has been clearly explained already: #26262 (comment)
kitwestneat commentedon Oct 4, 2018
Ok, thanks for the explanation.
RyanCavanaugh commentedon Oct 4, 2018
Let's look at a longer example:
fox1t commentedon Oct 4, 2018
Ok, this is really a nice example! Love it.
Don't you think that the real problem is before
||operation. What is now bothering me is:n => dst.push(n)has(n: any) => numbertype and the callback parameter is declared ascallback: (arg: T) => void). Isn't this already a problem on its own that I am allowed to pass a callback with wrong signature?6 remaining items
fox1t commentedon Oct 5, 2018
So, wrapping up all of the great comments here, I think we can say that
voidis meant to be used for functions that only have side-effects and that is different both fromanyandundefined, even ifconst bar: void = undefinedis valid syntax.There is still one question left though. As i pointed out before, official TS docs say "Declaring variables of type void is not useful because you can only assign undefined or null to them", but trying to write
const foo: void = nullgivesType 'null' is not assignable to type 'void'.error. Docs update needed?earshinov commentedon Oct 19, 2018
Maksim Sinik (@fox1t) , I believe that docs are correct.
You have this error because you have
strictNullChecks: true(orstrict: truefor that matter) in yourtsconfig.json. By defaultstrictNullChecksisfalse, andvoidis assignable fromnull.Btw, I've got a nice type compatibility table. Paste it into any editor capable of highlighting TypeScript errors and you will see which types are compatible and which are not (depending on the active
tsconfig.json).Demo screenshot:
Update: Published this table in a dedicated repo:
https://github.com/earshinov/typescript-type-compatibility-table
RyanCavanaugh commentedon Oct 19, 2018
earshinov that is gorgeous 😍
connorjclark commentedon Oct 19, 2018
Adding unknown to above :)
igpeev commentedon Dec 9, 2018
Guys, is there a solution to this issue ?
Here's how it affects my project. I use swagger plugin (swagger-codegen-maven-plugin) in JAVA Spring project to generate the Angular/TS API (i.e. front-end client services and interfaces/modules). Now there's a manual step of fixing the autogenerated code, as the Angular project doesn't build due to this breaking change in TS (Use of 'void' in control flow constructs is now disallowed Error).
So, is TS going to fix this (I tested in Chrome Console JS works just fine with void evaluating to falsy) OR it is going to be a deviation from JS from now on in respect to this particular functionality ?
P.S. I also tested manual generators available, but all work probably with the same template and cannot escape the error in the generated code unless manual fixing it ... Thanks if s.o. pays attn to this
ackvf commentedon Feb 20, 2019
I was using the
console.debug(props) ||for quickly debugging just about everything, I even have a snippet for that. With the comma separator, it is necessary to also enclose the code within brackets.Can I suppress the error or can I augment the console declaration to look like it's returning a falsy value?
//EDIT
Okay, this makes the job done.
thscott commentedon Feb 24, 2019
Looks like you can still coerce a void function to a number, and then do a truthiness check, so
+console.log("log") || aFn()works to avoid the outer brackets needed with the comma operator.sharno commentedon Nov 21, 2019
Why is
voidallowed to be checked for truthiness when it's a discriminated union with another type?Do you think I should file a bug for this?
weswigham commentedon Nov 21, 2019
voidis a bad type that predatesunknownandundefinedandnulland how it works is by and large an artifact of how it used to work (ie, we generally don't try to change howvoidbehaves - we change how other things behave in the presence ofvoidinstead). If you're writing new type definitions today, it's almost always better to declare things of typeunknownorundefined, depending on your intent, instead ofvoid(which sort of acts like either, depending on context, which is why it's so inconsistent).ackvf commentedon Nov 26, 2019
This still bothers me a bit though
stavalfi commentedon Dec 2, 2019
Ryan Cavanaugh (@RyanCavanaugh)
.then(x=> console.log(x) || x)is a type error.How can we turn off this rule?
ackvf commentedon Dec 30, 2019
Stav Alfi (@stavalfi)
just coerce the console.log to a not a number
OR augment the type
use comma operator instead of || because void type cannot be tested f…