Skip to content

Disallow expressions of type void to be used in truthiness checks #26234

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

Merged
merged 2 commits into from
Aug 7, 2018

Conversation

RyanCavanaugh
Copy link
Member

Fixes an offhand comment from #17892

This found a bug (!) in session.ts - reloadFromFile returned void so we were never hitting this line. I added return statements to reloadFromFile based on what I think it should do but it's not immediately apparent.

        private reload(args: protocol.ReloadRequestArgs, reqSeq: number) {
            const file = toNormalizedPath(args.file);
            const tempFileName = args.tmpfile === undefined ? undefined : toNormalizedPath(args.tmpfile);
            const info = this.projectService.getScriptInfoForNormalizedPath(file);
            if (info) {
                this.changeSeq++;
                // make sure no changes happen before this one is finished
                if (info.reloadFromFile(tempFileName)) {
                    this.doOutput(/*info*/ undefined, CommandNames.Reload, reqSeq, /*success*/ true);
/*---------         !!!!!!!!!!!!! unreachable! */
                }
            }
        }

@RyanCavanaugh
Copy link
Member Author

@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 6, 2018

Heya @RyanCavanaugh, I've started to run the extended test suite on this PR at 14d3c69. You can monitor the build here. It should now contribute to this PR's status checks.

@RyanCavanaugh
Copy link
Member Author

@weswigham is it possible to view the RWC diffs?

@weswigham
Copy link
Member

Not easily (or without doubling the build time). I think I could probably surface them as an extra vsts pr without too much consternation - I'll look into it. Although we should also strive for usually having a clean RWC run, too. ❤️

Copy link
Contributor

@ajafff ajafff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the condition in a ConditionalExpresson a ? b : c, the LHS of a && b and a || b and the operand of prefix unary negation !a?

@RyanCavanaugh
Copy link
Member Author

New RWC failures:

https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/src/core/util/Tree.ts#L156 - this function accepts an (arg: T) => void callback and tests its result - basically a type error but not a real bug per se in this file (may be an error in other places though)

VS Code has a similar issue: https://github.com/Microsoft/vscode/blob/master/src/vs/base/parts/tree/browser/treeDefaults.ts#L268

@weswigham
Copy link
Member

@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 6, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at 14d3c69. You can monitor the build here. It should now contribute to this PR's status checks.

Copy link
Member

@ahejlsberg ahejlsberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also include this check in conditional expressions.

checkSourceElement(node.statement);
}

function checkTruthinessExpression(node: Expression) {
const type = checkExpression(node);
if (type === voidType) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better way to write this is if (type.flags & TypeFlags.Void) as we may at some point introduce other type instances that represent a void type.

@weswigham
Copy link
Member

@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 6, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at ca10b7a. You can monitor the build here. It should now contribute to this PR's status checks.

@RyanCavanaugh
Copy link
Member Author

lodash RWC turned up one "not obviously broken" example (simplified):

const noop = () => { };
const blah = () => 5;
var fn = Math.random() > 0.5 ? noop : blah;
if (fn()) {

}

Due to subtype reduction, fn is () => void and we issue an error.

RWC also found some code in VS Code (since fixed) where they were writing

assert.ok(fs.exists("foo"));

where fs.exists is (filename: string, callback: (r: boolean) => void) => void, so very much a real bug.

@weswigham
Copy link
Member

@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 7, 2018

Heya @weswigham, I've started to run the extended test suite on this PR at ca10b7a. You can monitor the build here. It should now contribute to this PR's status checks.

@ahejlsberg
Copy link
Member

@RyanCavanaugh I agree the lodash example isn't obviously broken, but it definitely is suspicious given that a function returning anything can be assigned to a function returning void. So, with that in mind, I think it is a reasonable catch.

@chharvey
Copy link

chharvey commented Oct 4, 2018

I don't think this should have been done. A lot of times people use void in a logical OR operator to simplify code:

before:

let timesTwo = (x: number) => {
  console.log(`original: ${x}`);
  return x * 2;
}

after:

let timesTwo = (x: number) => console.log(`original: ${x}`) || x * 2;

another example:

let timesTwo = (x: number) => assert(!Number.isNaN(x) && Number.isFinite(x), `Must provide a finite number.`) || x * 2

because console.log(), assert(), etc. are of type void, TypeScript v3.1.1 throws this error:

error TS1345: An expression of type 'void' cannot be tested for truthiness

@Jessidhia
Copy link

That looks obfuscated, not simplified... if you really want to do something like that that still clearly shows the side-effect nature of your call, you should use the comma operator.

let timesTwo = (x: number) => (console.log(`original: ${x}`), x * 2);

@RyanCavanaugh
Copy link
Member Author

Let's take all discussion to #26262

@microsoft microsoft locked as resolved and limited conversation to collaborators Oct 4, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants