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 (tree walk version) #33178

Merged
merged 5 commits into from Sep 25, 2019

Conversation

@jwbay
Copy link
Contributor

jwbay commented Aug 31, 2019

This is an alternate version of #32802 -- see that one for context.

This version attempts to prevent false positives by doing a syntax walk to see if the checked function is used, as opposed to relying on a heuristic where the function returns a boolean. The first commit is the same for both PR; the second adds filtering that differs.

Opened as a separate PR by request (cc @RyanCavanaugh)

Notably, this change flagged a couple things in the compiler itself. Commented below for both.

@@ -1362,18 +1362,16 @@ namespace ts {
// try to verify results of module resolution
for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.originalFileName, currentDirectory);
if (resolveModuleNamesWorker) {

This comment has been minimized.

Copy link
@jwbay

jwbay Sep 1, 2019

Author Contributor

Looks like resolveModuleNamesWorker is always assigned above, so this was a dead check

@@ -1653,7 +1653,7 @@ namespace ts {
*/
export function compose<T>(...args: ((t: T) => T)[]): (t: T) => T;
export function compose<T>(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T {
if (e) {
if (!!e) {

This comment has been minimized.

Copy link
@jwbay

jwbay Sep 1, 2019

Author Contributor

This is a pretty good example of what TS would now consider suspicious-enough code to error on. Making e optional would also have fixed this.

@orta

This comment has been minimized.

Copy link
Member

orta commented Sep 11, 2019

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

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

typescript-bot commented Sep 11, 2019

Heya @orta, I've started to run the extended test suite on this PR at 3c9e338. 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

typescript-bot commented Sep 11, 2019

Heya @orta, I've started to run the parallelized community code test suite on this PR at 3c9e338. 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

typescript-bot commented Sep 11, 2019

Heya @orta, I've started to run the parallelized Definitely Typed test suite on this PR at 3c9e338. 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

typescript-bot commented Sep 11, 2019

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

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Sep 11, 2019

The RWC diff is unrelated.

The User Test Suite diff is a false positive, but not a bad one: https://github.com/npm/cli/blob/latest/test/tap/check-permissions.js#L27

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Sep 11, 2019

@typescript-bot perf test this

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

typescript-bot commented Sep 11, 2019

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

Update: The results are in!

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

typescript-bot commented Sep 11, 2019

@RyanCavanaugh
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..33178

Metric master 33178 Delta Best Worst
Angular - node (v12.1.0, x64)
Memory used 331,291k (± 0.02%) 325,798k (± 0.04%) -5,493k (- 1.66%) 325,266k 325,968k
Parse Time 1.56s (± 0.54%) 1.48s (± 0.45%) -0.08s (- 4.87%) 1.47s 1.50s
Bind Time 0.78s (± 0.67%) 0.75s (± 0.63%) -0.03s (- 3.34%) 0.75s 0.77s
Check Time 4.25s (± 0.60%) 4.19s (± 0.47%) -0.06s (- 1.48%) 4.14s 4.24s
Emit Time 5.22s (± 0.59%) 5.23s (± 0.48%) +0.01s (+ 0.21%) 5.17s 5.28s
Total Time 11.82s (± 0.40%) 11.66s (± 0.30%) -0.16s (- 1.32%) 11.55s 11.74s
Monaco - node (v12.1.0, x64)
Memory used 346,015k (± 0.04%) 345,898k (± 0.03%) -116k (- 0.03%) 345,732k 346,088k
Parse Time 1.22s (± 0.60%) 1.22s (± 0.91%) +0.00s (+ 0.16%) 1.20s 1.24s
Bind Time 0.68s (± 0.74%) 0.68s (± 1.47%) +0.01s (+ 1.33%) 0.67s 0.72s
Check Time 4.25s (± 0.51%) 4.26s (± 0.67%) +0.01s (+ 0.26%) 4.20s 4.34s
Emit Time 2.88s (± 1.37%) 2.86s (± 0.68%) -0.02s (- 0.70%) 2.81s 2.91s
Total Time 9.02s (± 0.58%) 9.03s (± 0.31%) +0.00s (+ 0.03%) 8.98s 9.11s
TFS - node (v12.1.0, x64)
Memory used 301,407k (± 0.02%) 301,420k (± 0.02%) +13k (+ 0.00%) 301,314k 301,503k
Parse Time 0.95s (± 0.98%) 0.94s (± 0.72%) -0.00s (- 0.42%) 0.93s 0.96s
Bind Time 0.63s (± 1.03%) 0.62s (± 1.33%) -0.00s (- 0.64%) 0.61s 0.65s
Check Time 3.86s (± 0.41%) 3.85s (± 0.54%) -0.00s (- 0.13%) 3.79s 3.90s
Emit Time 2.97s (± 0.46%) 2.96s (± 0.49%) -0.00s (- 0.13%) 2.93s 3.00s
Total Time 8.40s (± 0.24%) 8.38s (± 0.38%) -0.02s (- 0.18%) 8.33s 8.48s
Angular - node (v8.9.0, x64)
Memory used 350,095k (± 0.02%) 344,535k (± 0.01%) -5,559k (- 1.59%) 344,452k 344,644k
Parse Time 2.09s (± 0.42%) 1.99s (± 0.43%) -0.10s (- 4.83%) 1.97s 2.01s
Bind Time 0.84s (± 0.57%) 0.82s (± 0.79%) -0.02s (- 2.27%) 0.80s 0.83s
Check Time 5.12s (± 0.59%) 5.04s (± 0.75%) -0.08s (- 1.52%) 4.97s 5.12s
Emit Time 5.97s (± 0.68%) 6.15s (± 0.63%) +0.18s (+ 3.00%) 6.08s 6.25s
Total Time 14.02s (± 0.37%) 14.00s (± 0.48%) -0.02s (- 0.14%) 13.89s 14.16s
Monaco - node (v8.9.0, x64)
Memory used 363,686k (± 0.01%) 363,687k (± 0.01%) +2k (+ 0.00%) 363,550k 363,794k
Parse Time 1.56s (± 0.52%) 1.57s (± 0.32%) +0.00s (+ 0.32%) 1.55s 1.57s
Bind Time 0.89s (± 1.12%) 0.89s (± 1.01%) -0.00s (- 0.45%) 0.86s 0.90s
Check Time 5.11s (± 1.30%) 5.09s (± 1.22%) -0.02s (- 0.47%) 5.00s 5.26s
Emit Time 3.14s (± 4.52%) 3.25s (± 3.67%) +0.11s (+ 3.44%) 2.90s 3.39s
Total Time 10.70s (± 0.89%) 10.79s (± 0.68%) +0.09s (+ 0.82%) 10.60s 10.98s
TFS - node (v8.9.0, x64)
Memory used 317,706k (± 0.01%) 317,645k (± 0.01%) -60k (- 0.02%) 317,584k 317,721k
Parse Time 1.26s (± 0.53%) 1.25s (± 0.54%) -0.01s (- 0.71%) 1.24s 1.27s
Bind Time 0.70s (± 5.02%) 0.68s (± 3.63%) -0.02s (- 2.99%) 0.66s 0.78s
Check Time 4.45s (± 1.36%) 4.49s (± 0.87%) +0.04s (+ 0.83%) 4.36s 4.54s
Emit Time 3.09s (± 0.60%) 3.08s (± 0.87%) -0.01s (- 0.45%) 3.00s 3.12s
Total Time 9.50s (± 0.52%) 9.50s (± 0.44%) -0.00s (- 0.03%) 9.37s 9.57s
Angular - node (v8.9.0, x86)
Memory used 198,187k (± 0.02%) 195,180k (± 0.02%) -3,007k (- 1.52%) 195,060k 195,274k
Parse Time 2.03s (± 0.47%) 1.92s (± 0.47%) -0.11s (- 5.28%) 1.90s 1.94s
Bind Time 0.95s (± 1.16%) 0.94s (± 0.43%) -0.01s (- 1.26%) 0.93s 0.95s
Check Time 4.66s (± 0.45%) 4.57s (± 0.52%) -0.09s (- 1.85%) 4.51s 4.62s
Emit Time 5.70s (± 0.56%) 5.82s (± 1.76%) +0.11s (+ 2.00%) 5.58s 6.08s
Total Time 13.35s (± 0.34%) 13.25s (± 0.78%) -0.10s (- 0.73%) 13.07s 13.54s
Monaco - node (v8.9.0, x86)
Memory used 203,221k (± 0.02%) 203,189k (± 0.03%) -33k (- 0.02%) 203,062k 203,346k
Parse Time 1.62s (± 0.78%) 1.61s (± 0.69%) -0.01s (- 0.37%) 1.60s 1.64s
Bind Time 0.72s (± 0.77%) 0.72s (± 0.65%) +0.00s (+ 0.28%) 0.71s 0.73s
Check Time 4.89s (± 0.59%) 4.86s (± 0.58%) -0.03s (- 0.70%) 4.80s 4.94s
Emit Time 3.19s (± 0.73%) 3.15s (± 0.64%) -0.03s (- 1.04%) 3.12s 3.22s
Total Time 10.41s (± 0.50%) 10.34s (± 0.38%) -0.07s (- 0.67%) 10.26s 10.46s
TFS - node (v8.9.0, x86)
Memory used 178,509k (± 0.01%) 178,518k (± 0.01%) +10k (+ 0.01%) 178,460k 178,582k
Parse Time 1.31s (± 0.72%) 1.32s (± 0.88%) +0.00s (+ 0.15%) 1.30s 1.34s
Bind Time 0.65s (± 0.77%) 0.64s (± 1.75%) -0.00s (- 0.62%) 0.63s 0.68s
Check Time 4.31s (± 0.61%) 4.28s (± 0.53%) -0.03s (- 0.79%) 4.23s 4.32s
Emit Time 2.88s (± 1.00%) 2.86s (± 1.53%) -0.02s (- 0.69%) 2.77s 2.96s
Total Time 9.15s (± 0.38%) 9.10s (± 0.60%) -0.05s (- 0.58%) 8.97s 9.24s
Angular - node (v9.0.0, x64)
Memory used 349,762k (± 0.03%) 344,113k (± 0.02%) -5,648k (- 1.61%) 343,976k 344,249k
Parse Time 1.82s (± 0.45%) 1.72s (± 0.64%) -0.10s (- 5.49%) 1.69s 1.74s
Bind Time 0.78s (± 0.77%) 0.77s (± 0.64%) -0.01s (- 1.16%) 0.76s 0.78s
Check Time 4.88s (± 0.65%) 4.76s (± 0.38%) -0.12s (- 2.54%) 4.73s 4.79s
Emit Time 5.78s (± 1.32%) 5.70s (± 1.63%) -0.08s (- 1.42%) 5.52s 5.89s
Total Time 13.26s (± 0.68%) 12.95s (± 0.86%) -0.31s (- 2.35%) 12.74s 13.16s
Monaco - node (v9.0.0, x64)
Memory used 363,480k (± 0.02%) 363,441k (± 0.02%) -40k (- 0.01%) 363,284k 363,702k
Parse Time 1.32s (± 0.61%) 1.32s (± 0.46%) -0.00s (- 0.23%) 1.30s 1.33s
Bind Time 0.83s (± 1.49%) 0.84s (± 1.13%) +0.01s (+ 1.44%) 0.81s 0.86s
Check Time 5.07s (± 1.60%) 4.92s (± 0.89%) -0.15s (- 2.92%) 4.86s 5.08s
Emit Time 3.08s (± 5.47%) 3.30s (± 3.09%) +0.22s (+ 7.13%) 2.90s 3.40s
Total Time 10.30s (± 1.04%) 10.38s (± 0.74%) +0.08s (+ 0.81%) 10.10s 10.50s
TFS - node (v9.0.0, x64)
Memory used 317,468k (± 0.01%) 317,422k (± 0.01%) -46k (- 0.01%) 317,296k 317,557k
Parse Time 1.04s (± 0.50%) 1.05s (± 0.35%) +0.01s (+ 0.48%) 1.04s 1.05s
Bind Time 0.62s (± 0.72%) 0.62s (± 0.79%) +0.00s (+ 0.32%) 0.61s 0.63s
Check Time 4.38s (± 0.57%) 4.38s (± 0.42%) -0.00s (- 0.02%) 4.35s 4.42s
Emit Time 3.20s (± 0.78%) 3.19s (± 0.41%) -0.00s (- 0.13%) 3.17s 3.22s
Total Time 9.24s (± 0.51%) 9.24s (± 0.27%) -0.00s (- 0.01%) 9.19s 9.30s
Angular - node (v9.0.0, x86)
Memory used 198,329k (± 0.02%) 195,251k (± 0.04%) -3,079k (- 1.55%) 195,113k 195,376k
Parse Time 1.73s (± 0.58%) 1.63s (± 0.38%) -0.10s (- 5.99%) 1.62s 1.65s
Bind Time 0.90s (± 0.56%) 0.89s (± 0.87%) -0.00s (- 0.45%) 0.87s 0.91s
Check Time 4.35s (± 0.50%) 4.25s (± 0.30%) -0.10s (- 2.25%) 4.23s 4.28s
Emit Time 5.53s (± 0.69%) 5.51s (± 0.28%) -0.03s (- 0.49%) 5.48s 5.56s
Total Time 12.51s (± 0.37%) 12.28s (± 0.18%) -0.23s (- 1.83%) 12.24s 12.33s
Monaco - node (v9.0.0, x86)
Memory used 203,274k (± 0.02%) 203,274k (± 0.03%) +1k (+ 0.00%) 203,155k 203,372k
Parse Time 1.36s (± 0.74%) 1.35s (± 0.52%) -0.01s (- 0.44%) 1.33s 1.36s
Bind Time 0.64s (± 1.18%) 0.65s (± 0.73%) +0.00s (+ 0.31%) 0.64s 0.66s
Check Time 4.72s (± 0.48%) 4.69s (± 0.49%) -0.03s (- 0.61%) 4.64s 4.74s
Emit Time 3.10s (± 1.07%) 3.10s (± 0.52%) -0.00s (- 0.03%) 3.07s 3.15s
Total Time 9.82s (± 0.48%) 9.79s (± 0.31%) -0.03s (- 0.31%) 9.72s 9.85s
TFS - node (v9.0.0, x86)
Memory used 178,596k (± 0.02%) 178,585k (± 0.02%) -11k (- 0.01%) 178,504k 178,673k
Parse Time 1.07s (± 0.54%) 1.07s (± 0.65%) +0.00s (+ 0.37%) 1.05s 1.08s
Bind Time 0.58s (± 0.57%) 0.58s (± 0.86%) -0.00s (- 0.69%) 0.57s 0.59s
Check Time 4.16s (± 0.55%) 4.15s (± 1.17%) -0.01s (- 0.14%) 4.06s 4.30s
Emit Time 2.79s (± 1.85%) 2.79s (± 0.99%) -0.00s (- 0.07%) 2.71s 2.85s
Total Time 8.59s (± 0.64%) 8.59s (± 0.86%) -0.01s (- 0.08%) 8.45s 8.81s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-161-generic
Architecturex64
Available Memory16 GB
Available Memory10 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v12.1.0, x64)
  • node (v8.9.0, x64)
  • node (v8.9.0, x86)
  • node (v9.0.0, x64)
  • node (v9.0.0, x86)
Scenarios
  • Angular - node (v12.1.0, x64)
  • Angular - node (v8.9.0, x64)
  • Angular - node (v8.9.0, x86)
  • Angular - node (v9.0.0, x64)
  • Angular - node (v9.0.0, x86)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v8.9.0, x64)
  • Monaco - node (v8.9.0, x86)
  • Monaco - node (v9.0.0, x64)
  • Monaco - node (v9.0.0, x86)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v8.9.0, x64)
  • TFS - node (v8.9.0, x86)
  • TFS - node (v9.0.0, x64)
  • TFS - node (v9.0.0, x86)
Benchmark Name Iterations
Current 33178 10
Baseline master 10

@RyanCavanaugh RyanCavanaugh requested a review from orta Sep 13, 2019
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.7.0 milestone Sep 13, 2019
@orta

This comment has been minimized.

Copy link
Member

orta commented Sep 23, 2019

Hrm, none of these builds have accessible logs.

I bet it's likely that community projects will need changes like:

-        if (e) {
+       if (!!e) {

Will re-run and take a look.

@typescript-bot test this
@typescript-bot user test this

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

typescript-bot commented Sep 23, 2019

Heya @orta, I've started to run the extended test suite on this PR at 3c9e338. 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

typescript-bot commented Sep 23, 2019

Heya @orta, I've started to run the parallelized community code test suite on this PR at 3c9e338. 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

typescript-bot commented Sep 23, 2019

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

@orta

This comment has been minimized.

Copy link
Member

orta commented Sep 23, 2019

OK @jwbay - to get this mergeable you'll need to accept jwbay#3

jwbay and others added 2 commits Sep 24, 2019
…nonNullableCallSignaturesTreeWalk

🤖 User test baselines have changed for nonNullableCallSignaturesTreeWalk
@orta

This comment has been minimized.

Copy link
Member

orta commented Sep 25, 2019

OK, perf looks acceptable to me.

The idea is a good one, and I feel good about the implementation - thanks @jwbay!

@orta orta merged commit 91be368 into microsoft:master Sep 25, 2019
5 checks passed
5 checks passed
continuous-integration/travis-ci/pr The Travis CI build passed
Details
license/cla All CLA requirements met.
Details
node10 Build #45702 succeeded
Details
node12 Build #45700 succeeded
Details
node8 Build #45701 succeeded
Details
@user753

This comment has been minimized.

Copy link

user753 commented Oct 2, 2019

@jwbay
Is there any reason why it affects only functions and not arrays, records, etc?

function foo(array: string[]) {
  if (array) {
    return true
  }
}

function bar(record: Record<string, string>) {
  if (record) {
    return true
  }
}
@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Oct 2, 2019

@user753 a few major problems appeared when we tried that approach.

Array access is optimistically assumed to be in-bounds, so code like this gets incorrectly flagged as an error. The same thing happens for Map.get and a few other APIs that are assumed to "always return non-undefined values when used correctly".

const arr = [rec, rec, rec];
const el = arr[someIndex];
if (el) {
  // do something
}

Separately, many definition files were written prior to the introduction of strictNullChecks, so correct null/undefined checks on properties from those definitions would get incorrectly flagged as an error.

Ultimately the very narrow check implemented here was the only one that didn't generate a huge amount of false positives.

@user753

This comment has been minimized.

Copy link

user753 commented Oct 3, 2019

@RyanCavanaugh Thanks.
Do I understand correctly that It isn't a problem for functions because a function inside an array isn't a common use case?

const arr = [func, func, func];
const el = arr[someIndex];
if (el) {
  // do something
}
@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Oct 3, 2019

Normally if that does happen, a call to el() appears in the body of the if, which is the additional check that this PR implements. If el *isn't` called, that'll be a false positive.

@RyanCavanaugh

This comment has been minimized.

@travigd

This comment has been minimized.

Copy link

travigd commented Oct 6, 2019

It would be great if this was extended to functions that return promises as well.

In the announcement for 3.7 (which I'm thrilled about!!!), you include this code example.

function doAdminThing(user: User) {
    if (user.isAdministrator) {
    //  ~~~~~~~~~~~~~~~~~~~~
    // error! This condition will always return true since the function is always defined.
    //        Did you mean to call it instead?t

It would be incredible if this was also extended to raise an error when the function returns a promise and it's not awaited before using it in a condition (I've personally been bitten by this exact bug before).

class User {
    // ...
    async isAdministrator() { /* ... */ }
}
function doAdminThing(user: User) {
    if (user.isAdministrator()) {
    //  ~~~~~~~~~~~~~~~~~~~~~~
    // error! This condition will always return true since a promise is always truthy.
    //        Did you mean to await it instead?

I tried searching for issues related to this but couldn't find any (it's possible that I just missed it because there are so many issues and I'm not sure what keywords to search).

I've definitely been bitten by a bug where I did something like this:

async function passwordMatches(email: string, password: string) {
  /* return true if the password matches the result in the database */
}

if (passwordMatches("foo@bar.gov", "passw0rd")) {
  /* issue auth token */
}
@omidkrad

This comment has been minimized.

Copy link

omidkrad commented Oct 24, 2019

Regarding Uncalled Function Checks, I think it'd be pretty safe to also error on this:

function process() {
  console.log('Processing...');
}

process;
@BernsteinA

This comment has been minimized.

Copy link

BernsteinA commented on 006a327 Nov 18, 2019

nice

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