-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Breaking change: Use of 'void' in control flow constructs is now disallowed #26262
Comments
This is now an error. |
Hi, I just updated my CI/CD pipeline env to 3.1.1 and I encounter this error while building docker image. (err: Error) => (err ? console.log('Upload failed.') || rej(err) : console.log('Upload completed.')) 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. I think that TS here forgets that it is a JS superset and this makes me sad. |
@fox1t the problem with assuming a void-returning function is going to return a falsy value is that this code is legal: var x: () => void = () => true;
// somethingImportant does not run
if (x() || somethingImportant()) {
} 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 |
@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 const foo = (bar) => bar && bar.baz
// or
const baz = (name) => name || 'defaultName' can be considered "illegal" because neither The following isn't legal code: (err: Error) =>
err ? console.log('Upload failed.'), rej(err) : console.log('Upload completed.') 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". :) |
Does this change mean that Typescript insteads to remove boolean coercion all together? 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 Moreover, code like |
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. |
@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 @RyanCavanaugh pleas elaborate "many people believe that a void-returning function will always return a falsy value, but this is not the case"? |
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
I think this has been clearly explained already: #26262 (comment) |
Ok, thanks for the explanation. |
Let's look at a longer example: function myForEach<T>(arr: T[], callback: (arg: T) => void) {
let i = 0;
// love too sequence with ||
while (i < arr.length) callback(arr[i]) || i++;
}
// Yep
myForEach([1, 2, 3], n => console.log(n));
// Let's copy this array into another array
let dst: number[] = [];
// Out of memory error, wtf?
myForEach([1, 2, 3], n => dst.push(n)); |
Ok, this is really a nice example! Love it. |
It's not a wrong signature; see https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-functions-returning-non-void-assignable-to-function-returning-void If you want a function returning |
Basically the entire point of the // Let's say the return type is any
function myForEach<T>(arr: T[], callback: (arg: T) => any) {
// No type error, crashes at runtime, sad
for (let i = 0; i < arr.length; i++) callback(arr[i]).argle.bargle;
} |
@RyanCavanaugh This is surprising to me, and probably many other programmers too. Can the docs be updated with some examples of this?
This is enlightening. I always understood Also, if users are expecting |
Something that returns Specifying that a function that happens to return a value to be ignored specifically returns This is especially a problem in contravariant positions -- for example, a function argument that returns void (e.g. |
@Kovensky That makes sense, but "I don't care what this returns and you should not care either" sounds a lot like Maybe a type hierarchy diagram would be helpful? Having |
In your chart, |
So, wrapping up all of the great comments here, I think we can say that |
@fox1t , I believe that docs are correct. You have this error because you have 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 // 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 = {};
/*
|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
// Demo screenshot: Update: Published this table in a dedicated repo: |
@earshinov that is gorgeous 😍 |
Adding unknown to above :)
|
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). 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 |
I was using the
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.
|
Looks like you can still coerce a void function to a number, and then do a truthiness check, so |
I apologize for bringing this up again after so much time, but I can see a couple of problems with the current state of things. 1.
|
Why is (a: A | void) => {
a ? a.b : undefined // works
}; Do you think I should file a bug for this? |
|
This still bothers me a bit though
|
How can we turn off this rule? |
|
just coerce the console.log to a not a number
OR augment the declaration
|
…or truthiness Reference: microsoft/TypeScript#26262
TypeScript Version: 3.1
Search Terms: boolean void truthy falsy if while do for not
An expression of type 'void' cannot be tested for truthiness
Code
Consider this code:
Expected behavior: The "Cool!" branch is actually unreachable because
existsAndIsCool
returnsvoid
, notboolean
. I should have been warned about this at some point.Actual behavior: No error, never cool 😢
Playground Link: Link
Related Issues:
PR #26234
#7256
#10942
#7306
The text was updated successfully, but these errors were encountered: