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

Flag non-nullable functions in `if` statements as errors #32802

Open
wants to merge 2 commits into
base: master
from

Conversation

@jwbay
Copy link
Contributor

commented Aug 10, 2019

Under --strictNullChecks, we now error on testing non-nullable function types in if statements if they're not called. This is a subset of #9041 as mentioned here: #9041 (comment).

Example:

function onlyErrorsWhenNonNullable(required: () => boolean, optional?: () => boolean) {
    if (required) { // now an error...
    }

    if (required()) { // ...because you probably meant this, which is still ok
    }

    if (optional) { // still ok
    }
}

Note the concern about third-party values coming in without a strictNullChecks context is still valid :

import { ThirdPartyType, create } from 'some-package'

const x: ThirdPartyType = create();

// this may now flag an error because the type definitions for some-package
// don't mark someFunc as nullable when it should be
if (x.someFunc) {
}

Should we make a recommendation here in the error message like casting to a nullable type?

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

commented Aug 12, 2019

@typescript-bot test this
@typescript-bot user test this
@typescript-bot run dt

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 12, 2019

Heya @RyanCavanaugh, I've started to run the parallelized community code test suite on this PR at 130615a. You can monitor the build here. It should now contribute to this PR's status checks.

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 12, 2019

Heya @RyanCavanaugh, I've started to run the parallelized Definitely Typed test suite on this PR at 130615a. You can monitor the build here. It should now contribute to this PR's status checks.

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 12, 2019

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

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

commented Aug 12, 2019

There should probably be a special case for if(!!expr) { so that there's an idiomatic non-casting workaround for checkJs scenarios

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 12, 2019

The user suite test run you requested has finished and failed. I've opened a PR with the baseline diff from master.

@falsandtru

This comment has been minimized.

Copy link
Contributor

commented Aug 13, 2019

I don't think this is the scope of strictNullChecks. This change injures the certainty and reliance of strictNullChecks. This change should be enabled by a new flag.

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

commented Aug 16, 2019

This didn't go nearly as well as expected, unfortunately.

RWC turned up over a hundred examples of code of the form

if (someExpr) {
    someExpr();
}

where someExpr isn't really provably not-undefined because:

  • It was a class property not covered by strictPropertyInitialization
  • It was an array or map property lookup, this might be undefined even though we generally pretend this never happens
  • It's a DOM property that isn't present in all browsers, so it's valid feature-detection code
  • It comes from props that might have bad JS callers (this one is more questionable)

Some of this also probably happened as codebases started out without strictNullChecks on, wrote code correctly to defensively handle undefineds, then turned SNC on and it just so happened that there weren't identifiable undefineds manifest in those positions after all.

However, there were a few hits some code that didn't call someExpr where it did appear to be a bug:

                if (this.isComponentMounted) {
                    ~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead?
                    this.setState({ isUnblocking: false });
                }

Further restricting the check to functions returning boolean might make the false positive : true positive ratio good enough to be acceptable.

@RyanCavanaugh
Copy link
Member

left a comment

Let's try with the "functions returning boolean only" approach and hope for good results

@RyanCavanaugh RyanCavanaugh self-assigned this Aug 16, 2019

@jwbay jwbay force-pushed the jwbay:testNonNullableCallSignatures branch 2 times, most recently from 3906a1e to e81f5ec Aug 18, 2019

@jwbay

This comment has been minimized.

Copy link
Contributor Author

commented Aug 18, 2019

Let's try with the "functions returning boolean only" approach and hope for good results

Added a commit for this 🤞

I imagine one way to achieve a far better signal-to-noise ratio would be checking syntax to see if and how the thing being tested is used inside the block body.

if (this.isComponentMounted) {
    // definitely suspicious because `isComponentMounted` isn't used in the block
    this.setState({ isUnblocking: false });
}

if (this.isComponentMounted) {
    // but probably ok if it's called in the block
    this.setState({ isUnblocking: this.isComponentMounted() });

    // or is passed to something else
    this.setState({ isUnblocking: someCheck(this.isComponentMounted) });
}

However... I'm guessing a brute-force syntax walk over arbitrarily deep children would be too expensive to perform here. Is there some way to attach information like this during an earlier syntax walk, like I imagine might be done for scope checking or CFA?

There should probably be a special case for if(!!expr) { so that there's an idiomatic non-casting workaround for checkJs scenarios

This happens to already work due to the way we're checking specific syntax kinds. Added a test for it

@jwbay jwbay force-pushed the jwbay:testNonNullableCallSignatures branch from e81f5ec to 327ff39 Aug 18, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.