Fix "never nullish" diagnostic missing expressions wrapped in parentheses
The "never nullish" diagnostic (TS2881) was not reported when the left operand of a ?? operator was wrapped in parentheses, causing valid errors to be silently ignored.
const x: { y: string | undefined } | undefined = undefined as any;
const foo = x?.y ?? `oops` ?? ""; // Error reported ✓
const bar = (x?.y ?? `oops`) ?? ""; // Error NOT reported (bug)
Changes
-
checker.ts: ModifiedisNotWithinNullishCoalesceExpressionto usewalkUpOuterExpressions()instead of directly checkingnode.parent, allowing it to traverse through parentheses and other outer expressions to find the actual parent binary expression -
Tests: Added
neverNullishThroughParentheses.tstest case and updatedpredicateSemantics.tsbaseline to include additional errors now correctly detected
Impact
The fix catches previously missed errors in cases like:
-
(expr ?? value) ?? fallback- now detects never-nullishvalue -
opt ?? (null ?? 1)- now detects never-nullish1 - Multiple levels of parenthesis nesting
Original prompt
This section details on the original issue you should resolve
<issue_title>"Never nullish" checks miss "never nullish" through pareans</issue_title> <issue_description>### 🔎 Search Terms
never nullish parenthesis
🕗 Version & Regression Information
- This changed between versions 5.8 and 5.9
⏯ Playground Link
https://www.typescriptlang.org/play/?#code/MYewdgzgLgBAHgLhgbxgTydATgSzAcxgB8YBXMAEwFMAzPKimAX2LMtvsYF43q6wGMAIYRhYNAG4AsAChZs0JFg0QIGDzgB+AHRoYmzTAAGqgA4Qj+wwCJr0mYugwARkKzqYACi26rxsxYAlH629grgECAANlTaUSD4nioggfaO0bHxia5YqUA
💻 Code
const x: { y: string | undefined } | undefined = undefined as any; const foo = x?.y ?? `oops` ?? ""; const bar = (x?.y ?? `oops`) ?? ""; console.log(foo); console.log(bar);🙁 Actual behavior
Error on
foo's expression, no error onbar's.🙂 Expected behavior
Error on both; both expressions are guaranteed to be
"oops".Additional information about the issue
No response</issue_description>
Comments on the Issue (you are @copilot in this section)
- Fixes microsoft/TypeScript#62787
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.
@typescript-bot test this @typescript-bot pack this
Starting jobs; this comment will be updated as builds start and complete.
| Command | Status | Results |
|---|---|---|
pack this |
✅ Started | ✅ Results |
test top400 |
✅ Started | ✅ Results |
user test this |
✅ Started | ✅ Results |
run dt |
✅ Started | ✅ Results |
perf test this faster |
✅ Started | 👀 Results |
Hey @RyanCavanaugh, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:
{
"devDependencies": {
"typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/166605/artifacts?artifactName=tgz&fileId=A2C1143A3223567266A61A051AE7DFAA71E2A7FAC36CCA19230F756A7766160902&fileName=/typescript-6.0.0-insiders.20251124.tgz"
}
}
and then running npm install.
There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;
Hey @RyanCavanaugh, the results of running the DT tests are ready.
Everything looks the same!
@RyanCavanaugh Here are the results of running the user tests with tsc comparing main and refs/pull/62789/merge:
There were infrastructure failures potentially unrelated to your change:
- 1 instance of "Git clone failed"
Otherwise...
Everything looks good!
@RyanCavanaugh 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 | 1 | 1 | ~ | ~ | ~ | p=1.000 n=6 |
| Symbols | 62,370 | 62,370 | ~ | ~ | ~ | p=1.000 n=6 |
| Types | 50,386 | 50,386 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 193,841k (± 0.81%) | 194,569k (± 0.96%) | ~ | 192,634k | 196,301k | p=0.378 n=6 |
| Parse Time | 1.30s (± 0.76%) | 1.30s (± 0.58%) | ~ | 1.29s | 1.31s | p=1.000 n=6 |
| Bind Time | 0.75s | 0.75s (± 0.54%) | ~ | 0.74s | 0.75s | p=0.405 n=6 |
| Check Time | 9.90s (± 0.46%) | 9.89s (± 0.33%) | ~ | 9.84s | 9.94s | p=0.935 n=6 |
| Emit Time | 2.73s (± 0.79%) | 2.74s (± 1.12%) | ~ | 2.70s | 2.78s | p=0.573 n=6 |
| Total Time | 14.68s (± 0.35%) | 14.68s (± 0.15%) | ~ | 14.66s | 14.72s | p=0.810 n=6 |
| angular-1 - node (v18.15.0, x64) | ||||||
| Errors | 2 | 2 | ~ | ~ | ~ | p=1.000 n=6 |
| Symbols | 956,047 | 956,047 | ~ | ~ | ~ | p=1.000 n=6 |
| Types | 415,881 | 415,881 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 1,255,006k (± 0.00%) | 1,255,010k (± 0.00%) | ~ | 1,254,967k | 1,255,062k | p=0.936 n=6 |
| Parse Time | 6.52s (± 0.76%) | 6.56s (± 0.73%) | ~ | 6.47s | 6.61s | p=0.332 n=6 |
| Bind Time | 1.96s (± 0.21%) | 1.96s (± 0.32%) | ~ | 1.95s | 1.97s | p=0.673 n=6 |
| Check Time | 32.40s (± 0.25%) | 32.39s (± 0.32%) | ~ | 32.26s | 32.52s | p=0.873 n=6 |
| Emit Time | 14.87s (± 0.27%) | 14.90s (± 0.28%) | ~ | 14.83s | 14.95s | p=0.261 n=6 |
| Total Time | 55.75s (± 0.17%) | 55.80s (± 0.15%) | ~ | 55.69s | 55.95s | p=0.173 n=6 |
| mui-docs - node (v18.15.0, x64) | ||||||
| Errors | 0 | 0 | ~ | ~ | ~ | p=1.000 n=6 |
| Symbols | 2,717,214 | 2,717,214 | ~ | ~ | ~ | p=1.000 n=6 |
| Types | 937,297 | 937,297 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 3,044,183k (± 0.00%) | 3,044,181k (± 0.00%) | ~ | 3,044,113k | 3,044,274k | p=0.689 n=6 |
| Parse Time | 8.54s (± 0.29%) | 8.54s (± 0.40%) | ~ | 8.51s | 8.60s | p=0.936 n=6 |
| Bind Time | 2.32s (± 0.35%) | 2.32s (± 0.54%) | ~ | 2.30s | 2.33s | p=0.558 n=6 |
| Check Time | 93.14s (± 0.31%) | 93.14s (± 0.23%) | ~ | 92.80s | 93.35s | p=1.000 n=6 |
| Emit Time | 0.31s (± 2.44%) | 0.31s (± 1.68%) | ~ | 0.30s | 0.31s | p=0.784 n=6 |
| Total Time | 104.30s (± 0.29%) | 104.31s (± 0.19%) | ~ | 104.02s | 104.52s | p=1.000 n=6 |
| self-build-src - node (v18.15.0, x64) | ||||||
| Errors | 0 | 0 | ~ | ~ | ~ | p=1.000 n=6 |
| Symbols | 1,251,545 | 1,251,546 | +1 (+ 0.00%) | ~ | ~ | p=0.001 n=6 |
| Types | 259,756 | 259,756 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 2,514,295k (± 7.47%) | 2,514,152k (±11.83%) | ~ | 2,392,166k | 3,121,774k | p=0.298 n=6 |
| Parse Time | 5.17s (± 1.25%) | 5.19s (± 1.07%) | ~ | 5.11s | 5.25s | p=0.689 n=6 |
| Bind Time | 1.83s (± 0.98%) | 1.81s (± 0.81%) | ~ | 1.79s | 1.82s | p=0.067 n=6 |
| Check Time | 35.31s (± 0.77%) | 35.47s (± 0.47%) | ~ | 35.33s | 35.80s | p=0.872 n=6 |
| Emit Time | 3.03s (± 0.74%) | 2.99s (± 2.16%) | ~ | 2.92s | 3.11s | p=0.065 n=6 |
| Total Time | 45.34s (± 0.47%) | 45.48s (± 0.40%) | ~ | 45.32s | 45.78s | p=0.689 n=6 |
| self-build-src-public-api - node (v18.15.0, x64) | ||||||
| Errors | 0 | 0 | ~ | ~ | ~ | p=1.000 n=6 |
| Symbols | 1,251,545 | 1,251,546 | +1 (+ 0.00%) | ~ | ~ | p=0.001 n=6 |
| Types | 259,756 | 259,756 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 3,187,568k (± 0.03%) | 3,066,916k (± 9.64%) | ~ | 2,462,855k | 3,187,984k | p=0.936 n=6 |
| Parse Time | 6.81s (± 0.77%) | 6.77s (± 1.77%) | ~ | 6.58s | 6.91s | p=0.575 n=6 |
| Bind Time | 2.26s (± 2.19%) | 2.26s (± 1.39%) | ~ | 2.21s | 2.30s | p=0.748 n=6 |
| Check Time | 43.13s (± 0.29%) | 43.08s (± 0.27%) | ~ | 42.91s | 43.24s | p=0.575 n=6 |
| Emit Time | 3.52s (± 3.15%) | 3.48s (± 2.15%) | ~ | 3.39s | 3.58s | p=0.630 n=6 |
| Total Time | 55.73s (± 0.47%) | 55.59s (± 0.32%) | ~ | 55.34s | 55.83s | p=0.471 n=6 |
| self-compiler - node (v18.15.0, x64) | ||||||
| Errors | 0 | 0 | ~ | ~ | ~ | p=1.000 n=6 |
| Symbols | 264,177 | 264,178 | +1 (+ 0.00%) | ~ | ~ | p=0.001 n=6 |
| Types | 103,979 | 103,979 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 443,228k (± 0.01%) | 443,189k (± 0.01%) | ~ | 443,124k | 443,279k | p=0.230 n=6 |
| Parse Time | 3.52s (± 0.93%) | 3.52s (± 0.84%) | ~ | 3.48s | 3.55s | p=1.000 n=6 |
| Bind Time | 1.38s (± 1.64%) | 1.38s (± 0.76%) | ~ | 1.36s | 1.39s | p=0.451 n=6 |
| Check Time | 19.21s (± 0.23%) | 19.16s (± 0.37%) | ~ | 19.10s | 19.28s | p=0.171 n=6 |
| Emit Time | 1.54s (± 0.86%) | 1.53s (± 1.04%) | ~ | 1.51s | 1.55s | p=0.498 n=6 |
| Total Time | 25.64s (± 0.24%) | 25.59s (± 0.22%) | ~ | 25.52s | 25.67s | p=0.172 n=6 |
| ts-pre-modules - node (v18.15.0, x64) | ||||||
| Errors | 72 | 72 | ~ | ~ | ~ | p=1.000 n=6 |
| Symbols | 225,386 | 225,386 | ~ | ~ | ~ | p=1.000 n=6 |
| Types | 94,304 | 94,304 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 370,073k (± 0.02%) | 370,054k (± 0.02%) | ~ | 369,950k | 370,210k | p=0.689 n=6 |
| Parse Time | 2.85s (± 0.92%) | 2.84s (± 1.13%) | ~ | 2.81s | 2.88s | p=0.627 n=6 |
| Bind Time | 1.65s (± 1.37%) | 1.64s (± 1.48%) | ~ | 1.60s | 1.67s | p=0.566 n=6 |
| Check Time | 16.65s (± 0.13%) | 16.64s (± 0.13%) | ~ | 16.61s | 16.67s | p=0.373 n=6 |
| Emit Time | 0.00s | 0.00s | ~ | ~ | ~ | p=1.000 n=6 |
| Total Time | 21.15s (± 0.20%) | 21.12s (± 0.14%) | ~ | 21.08s | 21.16s | p=0.199 n=6 |
| vscode - node (v18.15.0, x64) | ||||||
| Errors | 11 | 12 | 🔻+1 (+ 9.09%) | ~ | ~ | p=0.001 n=6 |
| Symbols | 4,051,172 | 4,051,172 | ~ | ~ | ~ | p=1.000 n=6 |
| Types | 1,275,269 | 1,275,269 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 3,837,189k (± 0.01%) | 3,837,075k (± 0.00%) | ~ | 3,836,965k | 3,837,318k | p=0.575 n=6 |
| Parse Time | 15.60s (± 0.60%) | 15.55s (± 0.40%) | ~ | 15.46s | 15.63s | p=0.377 n=6 |
| Bind Time | 5.24s (± 0.45%) | 5.29s (± 0.85%) | +0.05s (+ 0.89%) | 5.25s | 5.37s | p=0.043 n=6 |
| Check Time | 113.01s (± 3.12%) | 111.71s (± 2.99%) | ~ | 107.29s | 117.31s | p=0.575 n=6 |
| Emit Time | 40.74s (±15.04%) | 41.24s (±25.40%) | ~ | 32.25s | 60.13s | p=0.810 n=6 |
| Total Time | 174.60s (± 5.18%) | 173.79s (± 5.90%) | ~ | 164.16s | 188.25s | p=0.936 n=6 |
| webpack - node (v18.15.0, x64) | ||||||
| Errors | 40 | 40 | ~ | ~ | ~ | p=1.000 n=6 |
| Symbols | 380,027 | 380,027 | ~ | ~ | ~ | p=1.000 n=6 |
| Types | 166,673 | 166,673 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 539,669k (± 0.03%) | 539,599k (± 0.02%) | ~ | 539,407k | 539,726k | p=0.173 n=6 |
| Parse Time | 4.62s (± 0.50%) | 4.62s (± 0.47%) | ~ | 4.59s | 4.65s | p=0.747 n=6 |
| Bind Time | 2.00s (± 0.61%) | 2.01s (± 1.02%) | ~ | 1.98s | 2.04s | p=0.254 n=6 |
| Check Time | 22.98s (± 1.04%) | 23.01s (± 1.62%) | ~ | 22.66s | 23.51s | p=0.873 n=6 |
| Emit Time | 0.00s | 0.00s | ~ | ~ | ~ | p=1.000 n=6 |
| Total Time | 29.60s (± 0.79%) | 29.63s (± 1.27%) | ~ | 29.31s | 30.17s | p=0.810 n=6 |
| xstate-main - node (v18.15.0, x64) | ||||||
| Errors | 30 | 30 | ~ | ~ | ~ | p=1.000 n=6 |
| Symbols | 690,397 | 690,397 | ~ | ~ | ~ | p=1.000 n=6 |
| Types | 208,790 | 208,790 | ~ | ~ | ~ | p=1.000 n=6 |
| Memory used | 586,175k (± 0.02%) | 586,122k (± 0.02%) | ~ | 585,995k | 586,283k | p=0.689 n=6 |
| Parse Time | 4.16s (± 0.87%) | 4.17s (± 0.78%) | ~ | 4.13s | 4.21s | p=1.000 n=6 |
| Bind Time | 1.41s (± 0.73%) | 1.41s (± 1.07%) | ~ | 1.39s | 1.42s | p=0.564 n=6 |
| Check Time | 20.77s (± 2.10%) | 20.84s (± 1.78%) | ~ | 20.27s | 21.15s | p=0.575 n=6 |
| Emit Time | 0.00s (±154.76%) | 0.00s (±154.76%) | ~ | 0.00s | 0.01s | p=1.000 n=6 |
| Total Time | 26.35s (± 1.66%) | 26.42s (± 1.44%) | ~ | 25.88s | 26.75s | p=0.936 n=6 |
- node (v18.15.0, x64)
- 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:
@RyanCavanaugh Here are the results of running the top 400 repos with tsc comparing main and refs/pull/62789/merge:
Everything looks good!