Skip to content

Account for right operands & fix a weird error message for leftmost nullish literals in checkNullishCoalesceOperands #59569

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

Conversation

ChiriVulpes
Copy link
Contributor

@ChiriVulpes ChiriVulpes commented Aug 9, 2024

Fixes #59546

This PR:

  1. Removes the error for nullish literals as the leftmost operand of an expression of all nullish coalescing operators.
  2. Performs the same checks for right-side operands that are not the last operand in an expression of all nullish coalescing operators.

Examples:

declare let opt: number | undefined;

const p01 = null ?? 1; // fixed error message, says "always nullish" instead of "never nullish"
const p02 = (opt ? null : undefined) ?? 1; // previously error, still error
const p03 = 1 ?? 2; // previously error, still error

const p10 = opt ?? null ?? 1; // previously OK, now errors on `null` as "always nullish"
const p11 = opt ?? (opt ? null : undefined) ?? 1; // previously OK, now errors on `opt ? null : undefined` as "always nullish"
const p12 = opt ?? (opt ? 1 : 2) ?? 3; // previously OK, now errors on `opt ? 1 : 2` as "never nullish"

The tests in predicateSemantics.ts have more cases to verify this works in all situations, but it's possible I've missed some and this needs more coverage.

For more information, see #59546

Note: this overview has been edited to reflect the current PR

@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Aug 9, 2024
@ChiriVulpes
Copy link
Contributor Author

@microsoft-github-policy-service agree

@ChiriVulpes ChiriVulpes force-pushed the check-non-rightmost-right-operands-and-ignore-leftmost-nullish-literals branch from 716a382 to cab0009 Compare August 9, 2024 03:24
@ChiriVulpes ChiriVulpes marked this pull request as ready for review August 9, 2024 03:48
@MartinJohns
Copy link
Contributor

const p01 = null ?? 1; // previously error, now OK

According to #59546 this should be an error.

@ChiriVulpes
Copy link
Contributor Author

No? That was the whole point of me making this PR 😛 Unless I’m misunderstanding what you mean? The PR accomplishes two things, it prevents erroring in the way I use it while preserving all other errors, and it adds new errors to right side operands that were previously not checked

@jakebailey
Copy link
Member

jakebailey commented Aug 9, 2024

I'm pretty sure Ryan was saying that the error message needed to be fixed to not complain about parens where that doesn't make sense, and that an unconditional nullish value on the left side was dubious code that should still error. I'm not sure how #59546 (comment) could be read any other way.

@ChiriVulpes
Copy link
Contributor Author

ChiriVulpes commented Aug 9, 2024

The error message was already fixed by another commit since the release of 5.6.0-beta, it was irrelevant to my issue. I failed to test in nightly when I made the initial issue report and it muddied everything. (I only noticed while working on this PR)

Copy link
Member

@RyanCavanaugh RyanCavanaugh left a comment

Choose a reason for hiding this comment

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

Most of the diff looks good but the special case for null ?? null etc is not an approved change.

@ChiriVulpes ChiriVulpes force-pushed the check-non-rightmost-right-operands-and-ignore-leftmost-nullish-literals branch from 72ea8bd to df5f297 Compare August 10, 2024 01:15
@ChiriVulpes
Copy link
Contributor Author

ChiriVulpes commented Aug 10, 2024

Okay so I've addressed the line you pointed out and some similar lines, unfortunately it increased the surface area of the PR a bit. I tried to limit it as much as I could.

List of changes:

  • The PredicateSemantics of binary expressions in getSyntacticNullishnessSemantics are now actually calculated like the ConditionalExpression, which results in situations like null ?? null ?? null actually erroring over the whole expression. As a result of these changes, the right operand check for never nullish is no longer necessary, so it was removed
  • Since nullish coalesce errors were all previously added to the operands of a nullish coalesce expression, null ?? null as an expression by itself was not able to be caught, so now the semantics of the left and right operands are used to add a new error if both operands are always nullish and that's the whole expression
  • Added a couple more test lines

Note that this does result in more overlapping errors in the baselines, but I think most cases in real-world code won't get much or any of that.

Hopefully this is what you wanted? Up to revert or do this differently if you prefer.

@ChiriVulpes
Copy link
Contributor Author

Actually maybe it would be more useful for me to explain the changes with two variations.

So the current code I just pushed results in errors like this:
image

The alternative would be reverting the change to start resolving PredicateSemantics of binary expresions and keeping the right side operand never nullish check, which looks like this:
image

I thought the former was probably closer to what you actually wanted though so that's what I went with. If you wanted something between the two of them, IE preferring the second version of p22 and p23, but wanting to error on more than just the middle null of null ?? null ?? null, I think it would result in the surface area of this PR getting even bigger than it already is

@MartinJohns
Copy link
Contributor

@RyanCavanaugh Can you clarify if I missed something? In the issue #59546 you said something like undefined ?? value should be an error, but this PR will allow null ?? value. Is null treated differently here, did you change your mind, or should null ?? value be an error?

Copy link
Member

@RyanCavanaugh RyanCavanaugh left a comment

Choose a reason for hiding this comment

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

I don't feel like we're getting a "meeting of the minds" here and the PR is still trying to create a carve-out for the behavior described in the original bug. The only change approved from that bug is to tweak the error message to not incorrectly talk about binary expressions when one exists. It might be cleaner to start fresh on a new PR that only makes that change?

@ChiriVulpes
Copy link
Contributor Author

ChiriVulpes commented Aug 12, 2024

As I've already stated, the error message being confusing was already fixed due to other changes since the initial beta release, I just forgot to test nightly when I made my initial bug report. [Edit: Apparently I was just hallucinating this, nevermind. I'm dumb]

I wanted to make a PR that addressed my issue in whatever way was most likely to actually be considered and pulled into the project. If it won't be, then that's fine, I guess. It's bad for me, because it means that if I want to write code that's doing this that's still just as comprehensible I'll have to be maintaining a TS fork or a TS patch indefinitely, but I have to do what I have to do and I don't fault anyone for having different opinions. I do think the eagerness of the error makes the part of it I have issue with a better fit for linting than an unconfigurable TS level error, but w/e

@ChiriVulpes ChiriVulpes changed the title Account for right operands & leftmost nullish literals in checkNullishCoalesceOperands Account for right operands & fix a weird error message for leftmost nullish literals in checkNullishCoalesceOperands Aug 13, 2024
@ChiriVulpes ChiriVulpes force-pushed the check-non-rightmost-right-operands-and-ignore-leftmost-nullish-literals branch from 789a0c2 to 2d84410 Compare August 13, 2024 11:03
@ChiriVulpes
Copy link
Contributor Author

This PR no longer addresses my issue, and instead only fixes the error message and adds in missing checks for the right operands. I don't think this will be controversial anymore

Copy link
Member

@RyanCavanaugh RyanCavanaugh left a comment

Choose a reason for hiding this comment

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

Thanks!

@jakebailey
Copy link
Member

This was approved but I think just not merged. @ChiriVulpes do you mind pulling in main? If you don't have time, I'm happy to do it for you.

@ChiriVulpes
Copy link
Contributor Author

@jakebailey I think it should be good now, it's been months since I touched the TS codebase so I might have done something wrong, though. Let me know if it needs tweaks

@ChiriVulpes
Copy link
Contributor Author

(The one fail appears to be unrelated and might go away with a rerun)

@jakebailey
Copy link
Member

@typescript-bot test it

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 21, 2025

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
test top400 ✅ Started 👀 Results
user test this ✅ Started ✅ Results
run dt ✅ Started ✅ Results
perf test this faster ✅ Started 👀 Results

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the user tests with tsc comparing main and refs/pull/59569/merge:

Everything looks good!

@typescript-bot
Copy link
Collaborator

Hey @jakebailey, the results of running the DT tests are ready.

Everything looks the same!

You can check the log here.

@typescript-bot
Copy link
Collaborator

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

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - node (v18.15.0, x64)
Errors 34 34 ~ ~ ~ p=1.000 n=6
Symbols 62,390 62,390 ~ ~ ~ p=1.000 n=6
Types 50,395 50,395 ~ ~ ~ p=1.000 n=6
Memory used 194,784k (± 0.97%) 192,931k (± 0.01%) ~ 192,894k 192,954k p=0.066 n=6
Parse Time 1.31s (± 0.31%) 1.31s (± 0.48%) ~ 1.30s 1.32s p=0.673 n=6
Bind Time 0.73s 0.73s ~ ~ ~ p=1.000 n=6
Check Time 9.75s (± 0.37%) 9.73s (± 0.37%) ~ 9.68s 9.79s p=0.289 n=6
Emit Time 2.73s (± 0.93%) 2.73s (± 0.60%) ~ 2.72s 2.76s p=0.357 n=6
Total Time 14.51s (± 0.20%) 14.51s (± 0.24%) ~ 14.45s 14.55s p=1.000 n=6
angular-1 - node (v18.15.0, x64)
Errors 37 37 ~ ~ ~ p=1.000 n=6
Symbols 948,488 948,488 ~ ~ ~ p=1.000 n=6
Types 411,006 411,006 ~ ~ ~ p=1.000 n=6
Memory used 1,224,192k (± 0.00%) 1,224,218k (± 0.00%) ~ 1,224,158k 1,224,261k p=0.298 n=6
Parse Time 6.63s (± 1.17%) 6.66s (± 0.60%) ~ 6.62s 6.74s p=0.808 n=6
Bind Time 1.89s (± 0.44%) 1.89s (± 0.33%) ~ 1.88s 1.90s p=0.340 n=6
Check Time 31.80s (± 0.09%) 31.80s (± 0.28%) ~ 31.68s 31.91s p=0.810 n=6
Emit Time 15.20s (± 0.31%) 15.23s (± 0.12%) ~ 15.21s 15.25s p=0.289 n=6
Total Time 55.52s (± 0.16%) 55.58s (± 0.15%) ~ 55.46s 55.67s p=0.296 n=6
mui-docs - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 2,347,440 2,347,440 ~ ~ ~ p=1.000 n=6
Types 845,526 845,526 ~ ~ ~ p=1.000 n=6
Memory used 2,120,414k (± 0.00%) 2,120,428k (± 0.00%) ~ 2,120,380k 2,120,481k p=0.378 n=6
Parse Time 7.26s (± 0.14%) 7.26s (± 0.10%) ~ 7.25s 7.27s p=0.611 n=6
Bind Time 2.46s (± 0.56%) 2.46s (± 0.68%) ~ 2.44s 2.48s p=0.676 n=6
Check Time 72.80s (± 0.32%) 72.84s (± 0.43%) ~ 72.36s 73.23s p=0.689 n=6
Emit Time 0.15s (± 3.77%) 0.14s (± 3.60%) ~ 0.14s 0.15s p=0.640 n=6
Total Time 82.67s (± 0.30%) 82.70s (± 0.38%) ~ 82.24s 83.09s p=0.810 n=6
self-build-src - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,226,564 1,226,585 +21 (+ 0.00%) ~ ~ p=0.001 n=6
Types 266,973 266,984 +11 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 2,356,701k (± 0.02%) 2,356,993k (± 0.03%) ~ 2,356,036k 2,358,222k p=0.575 n=6
Parse Time 5.18s (± 1.52%) 5.19s (± 0.56%) ~ 5.14s 5.22s p=0.936 n=6
Bind Time 1.76s (± 1.14%) 1.78s (± 1.30%) ~ 1.75s 1.81s p=0.165 n=6
Check Time 35.30s (± 0.18%) 35.30s (± 0.21%) ~ 35.20s 35.43s p=1.000 n=6
Emit Time 2.96s (± 1.90%) 2.99s (± 0.66%) ~ 2.96s 3.01s p=0.327 n=6
Total Time 45.23s (± 0.22%) 45.28s (± 0.13%) ~ 45.23s 45.38s p=0.230 n=6
self-build-src-public-api - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,226,564 1,226,585 +21 (+ 0.00%) ~ ~ p=0.001 n=6
Types 266,973 266,984 +11 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 3,032,019k (± 9.77%) 2,790,902k (±14.23%) ~ 2,426,614k 3,154,043k p=0.936 n=6
Parse Time 6.89s (± 0.91%) 6.85s (± 1.43%) ~ 6.72s 6.95s p=0.469 n=6
Bind Time 2.14s (± 1.43%) 2.15s (± 1.71%) ~ 2.09s 2.18s p=0.810 n=6
Check Time 42.83s (± 0.21%) 42.71s (± 0.34%) ~ 42.49s 42.86s p=0.230 n=6
Emit Time 3.53s (± 1.80%) 3.55s (± 3.64%) ~ 3.40s 3.75s p=0.810 n=6
Total Time 55.39s (± 0.14%) 55.26s (± 0.52%) ~ 54.86s 55.62s p=0.471 n=6
self-compiler - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 262,657 262,667 +10 (+ 0.00%) ~ ~ p=0.001 n=6
Types 106,839 106,849 +10 (+ 0.01%) ~ ~ p=0.001 n=6
Memory used 440,410k (± 0.01%) 440,443k (± 0.01%) ~ 440,360k 440,515k p=0.378 n=6
Parse Time 3.55s (± 1.37%) 3.56s (± 0.96%) ~ 3.51s 3.61s p=0.630 n=6
Bind Time 1.32s (± 1.00%) 1.33s (± 1.04%) ~ 1.31s 1.35s p=0.741 n=6
Check Time 18.96s (± 0.35%) 18.98s (± 0.36%) ~ 18.87s 19.07s p=0.747 n=6
Emit Time 1.51s (± 0.42%) 1.52s (± 1.30%) ~ 1.49s 1.54s p=0.655 n=6
Total Time 25.34s (± 0.38%) 25.38s (± 0.22%) ~ 25.31s 25.45s p=0.332 n=6
ts-pre-modules - node (v18.15.0, x64)
Errors 70 70 ~ ~ ~ p=1.000 n=6
Symbols 226,113 226,113 ~ ~ ~ p=1.000 n=6
Types 94,488 94,488 ~ ~ ~ p=1.000 n=6
Memory used 371,398k (± 0.06%) 371,402k (± 0.05%) ~ 371,275k 371,766k p=0.628 n=6
Parse Time 2.89s (± 0.86%) 2.89s (± 0.67%) ~ 2.86s 2.91s p=1.000 n=6
Bind Time 1.59s (± 0.73%) 1.59s (± 1.52%) ~ 1.56s 1.63s p=0.807 n=6
Check Time 16.44s (± 0.37%) 16.49s (± 0.35%) ~ 16.41s 16.58s p=0.227 n=6
Emit Time 0.00s 0.00s (±244.70%) ~ 0.00s 0.01s p=0.405 n=6
Total Time 20.92s (± 0.40%) 20.98s (± 0.37%) ~ 20.87s 21.09s p=0.228 n=6
vscode - node (v18.15.0, x64)
Errors 1 1 ~ ~ ~ p=1.000 n=6
Symbols 3,229,980 3,229,980 ~ ~ ~ p=1.000 n=6
Types 1,095,631 1,095,631 ~ ~ ~ p=1.000 n=6
Memory used 3,306,258k (± 0.01%) 3,306,395k (± 0.01%) ~ 3,305,874k 3,306,829k p=0.471 n=6
Parse Time 14.34s (± 0.38%) 14.38s (± 0.65%) ~ 14.25s 14.52s p=0.470 n=6
Bind Time 4.59s (± 0.70%) 4.61s (± 0.45%) ~ 4.58s 4.64s p=0.290 n=6
Check Time 89.06s (± 1.58%) 89.16s (± 1.23%) ~ 88.25s 90.78s p=0.810 n=6
Emit Time 28.34s (± 2.66%) 28.18s (± 2.67%) ~ 27.54s 29.24s p=1.000 n=6
Total Time 136.32s (± 0.98%) 136.32s (± 0.64%) ~ 135.25s 137.30s p=0.936 n=6
webpack - node (v18.15.0, x64)
Errors 2 2 ~ ~ ~ p=1.000 n=6
Symbols 293,866 293,866 ~ ~ ~ p=1.000 n=6
Types 119,628 119,628 ~ ~ ~ p=1.000 n=6
Memory used 447,193k (± 0.03%) 447,064k (± 0.04%) ~ 446,912k 447,304k p=0.230 n=6
Parse Time 4.09s (± 1.45%) 4.10s (± 1.45%) ~ 4.02s 4.16s p=0.688 n=6
Bind Time 1.77s (± 0.66%) 1.76s (± 1.51%) ~ 1.72s 1.80s p=0.742 n=6
Check Time 18.79s (± 0.65%) 18.82s (± 1.42%) ~ 18.64s 19.34s p=0.689 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 24.64s (± 0.68%) 24.68s (± 1.20%) ~ 24.45s 25.25s p=0.810 n=6
xstate-main - node (v18.15.0, x64)
Errors 5 5 ~ ~ ~ p=1.000 n=6
Symbols 559,094 559,094 ~ ~ ~ p=1.000 n=6
Types 187,011 187,011 ~ ~ ~ p=1.000 n=6
Memory used 496,214k (± 0.03%) 496,267k (± 0.01%) ~ 496,206k 496,326k p=0.471 n=6
Parse Time 3.41s (± 0.92%) 3.43s (± 0.51%) ~ 3.41s 3.46s p=0.195 n=6
Bind Time 1.19s (± 0.46%) 1.19s (± 1.47%) ~ 1.18s 1.22s p=0.869 n=6
Check Time 19.72s (± 1.90%) 19.63s (± 0.43%) ~ 19.53s 19.73s p=0.873 n=6
Emit Time 0.00s 0.00s (±244.70%) ~ 0.00s 0.01s p=0.405 n=6
Total Time 24.32s (± 1.54%) 24.26s (± 0.36%) ~ 24.16s 24.38s p=0.630 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Compiler-Unions - node (v18.15.0, x64)
  • angular-1 - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • ts-pre-modules - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate-main - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the top 400 repos with tsc comparing main and refs/pull/59569/merge:

Something interesting changed - please have a look.

Details

hoarder-app/hoarder

apps/workers/tsconfig.json

microsoft/vscode

5 of 55 projects failed to build with the old tsc and were ignored

extensions/microsoft-authentication/tsconfig.json

@jakebailey
Copy link
Member

jakebailey commented Feb 21, 2025

Both things above are correctly caught. In hoarder:

  feedData.items.forEach((item) => {
    item.guid = item.guid ?? `${item.id}` ?? item.link;
  });

${item.id} is definitely never nullish.

In vscode:

const id = `${claims.tid}/${(claims.oid ?? (claims.altsecid ?? '' + claims.ipd ?? ''))}`;

This parses like:

const id = `${claims.tid}/${(claims.oid ?? (claims.altsecid ?? ('' + claims.ipd) ?? ''))}`;

Clearly they meant:

const id = `${claims.tid}/${(claims.oid ?? ((claims.altsecid ?? '') + (claims.ipd ?? '')))}`;

@jakebailey jakebailey merged commit 2bed7fe into microsoft:main Feb 21, 2025
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"This binary expression is never nullish" errors due to the way I format my code
5 participants