-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Fix "never nullish" diagnostic missing expressions wrapped in parentheses #62789
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
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
6585b21
Initial plan
Copilot dc0d4ea
Fix never nullish checks to work through parentheses
Copilot 1961197
Update predicateSemantics baseline with new errors caught by fix
Copilot 29570a9
Totally different fix
RyanCavanaugh 5010485
Merge branch 'main' into copilot/fix-never-nullish-checks
RyanCavanaugh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
tests/baselines/reference/neverNullishThroughParentheses.errors.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| neverNullishThroughParentheses.ts(6,21): error TS2881: This expression is never nullish. | ||
| neverNullishThroughParentheses.ts(7,22): error TS2881: This expression is never nullish. | ||
| neverNullishThroughParentheses.ts(10,23): error TS2881: This expression is never nullish. | ||
| neverNullishThroughParentheses.ts(11,24): error TS2881: This expression is never nullish. | ||
| neverNullishThroughParentheses.ts(14,15): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. | ||
| neverNullishThroughParentheses.ts(15,16): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. | ||
| neverNullishThroughParentheses.ts(16,17): error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. | ||
| neverNullishThroughParentheses.ts(16,24): error TS2881: This expression is never nullish. | ||
|
|
||
|
|
||
| ==== neverNullishThroughParentheses.ts (8 errors) ==== | ||
| // Repro for issue where "never nullish" checks miss "never nullish" through parentheses | ||
|
|
||
| const x: { y: string | undefined } | undefined = undefined as any; | ||
|
|
||
| // Both should error - both expressions are guaranteed to be "oops" | ||
| const foo = x?.y ?? `oops` ?? ""; | ||
| ~~~~~~ | ||
| !!! error TS2881: This expression is never nullish. | ||
| const bar = (x?.y ?? `oops`) ?? ""; | ||
| ~~~~~~ | ||
| !!! error TS2881: This expression is never nullish. | ||
|
|
||
| // Additional test cases with various levels of nesting | ||
| const baz = ((x?.y ?? `oops`)) ?? ""; | ||
| ~~~~~~ | ||
| !!! error TS2881: This expression is never nullish. | ||
| const qux = (((x?.y ?? `oops`))) ?? ""; | ||
| ~~~~~~ | ||
| !!! error TS2881: This expression is never nullish. | ||
|
|
||
| // Test with different types | ||
| const str1 = ("literal") ?? "fallback"; | ||
| ~~~~~~~~~ | ||
| !!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. | ||
| const str2 = (("nested")) ?? "fallback"; | ||
| ~~~~~~~~ | ||
| !!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. | ||
| const nested = ("a" ?? "b") ?? "c"; | ||
| ~~~ | ||
| !!! error TS2869: Right operand of ?? is unreachable because the left operand is never nullish. | ||
| ~~~ | ||
| !!! error TS2881: This expression is never nullish. | ||
|
|
36 changes: 36 additions & 0 deletions
36
tests/baselines/reference/neverNullishThroughParentheses.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| //// [tests/cases/compiler/neverNullishThroughParentheses.ts] //// | ||
|
|
||
| //// [neverNullishThroughParentheses.ts] | ||
| // Repro for issue where "never nullish" checks miss "never nullish" through parentheses | ||
|
|
||
| const x: { y: string | undefined } | undefined = undefined as any; | ||
|
|
||
| // Both should error - both expressions are guaranteed to be "oops" | ||
| const foo = x?.y ?? `oops` ?? ""; | ||
| const bar = (x?.y ?? `oops`) ?? ""; | ||
|
|
||
| // Additional test cases with various levels of nesting | ||
| const baz = ((x?.y ?? `oops`)) ?? ""; | ||
| const qux = (((x?.y ?? `oops`))) ?? ""; | ||
|
|
||
| // Test with different types | ||
| const str1 = ("literal") ?? "fallback"; | ||
| const str2 = (("nested")) ?? "fallback"; | ||
| const nested = ("a" ?? "b") ?? "c"; | ||
|
|
||
|
|
||
| //// [neverNullishThroughParentheses.js] | ||
| "use strict"; | ||
| // Repro for issue where "never nullish" checks miss "never nullish" through parentheses | ||
| var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; | ||
| var x = undefined; | ||
| // Both should error - both expressions are guaranteed to be "oops" | ||
| var foo = (_b = (_a = x === null || x === void 0 ? void 0 : x.y) !== null && _a !== void 0 ? _a : "oops") !== null && _b !== void 0 ? _b : ""; | ||
| var bar = (_d = ((_c = x === null || x === void 0 ? void 0 : x.y) !== null && _c !== void 0 ? _c : "oops")) !== null && _d !== void 0 ? _d : ""; | ||
| // Additional test cases with various levels of nesting | ||
| var baz = (_f = (((_e = x === null || x === void 0 ? void 0 : x.y) !== null && _e !== void 0 ? _e : "oops"))) !== null && _f !== void 0 ? _f : ""; | ||
| var qux = (_h = ((((_g = x === null || x === void 0 ? void 0 : x.y) !== null && _g !== void 0 ? _g : "oops")))) !== null && _h !== void 0 ? _h : ""; | ||
| // Test with different types | ||
| var str1 = (_j = ("literal")) !== null && _j !== void 0 ? _j : "fallback"; | ||
| var str2 = (_k = (("nested"))) !== null && _k !== void 0 ? _k : "fallback"; | ||
| var nested = (_l = ("a" !== null && "a" !== void 0 ? "a" : "b")) !== null && _l !== void 0 ? _l : "c"; |
46 changes: 46 additions & 0 deletions
46
tests/baselines/reference/neverNullishThroughParentheses.symbols
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| //// [tests/cases/compiler/neverNullishThroughParentheses.ts] //// | ||
|
|
||
| === neverNullishThroughParentheses.ts === | ||
| // Repro for issue where "never nullish" checks miss "never nullish" through parentheses | ||
|
|
||
| const x: { y: string | undefined } | undefined = undefined as any; | ||
| >x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) | ||
| >y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) | ||
| >undefined : Symbol(undefined) | ||
|
|
||
| // Both should error - both expressions are guaranteed to be "oops" | ||
| const foo = x?.y ?? `oops` ?? ""; | ||
| >foo : Symbol(foo, Decl(neverNullishThroughParentheses.ts, 5, 5)) | ||
| >x?.y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) | ||
| >x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) | ||
| >y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) | ||
|
|
||
| const bar = (x?.y ?? `oops`) ?? ""; | ||
| >bar : Symbol(bar, Decl(neverNullishThroughParentheses.ts, 6, 5)) | ||
| >x?.y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) | ||
| >x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) | ||
| >y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) | ||
|
|
||
| // Additional test cases with various levels of nesting | ||
| const baz = ((x?.y ?? `oops`)) ?? ""; | ||
| >baz : Symbol(baz, Decl(neverNullishThroughParentheses.ts, 9, 5)) | ||
| >x?.y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) | ||
| >x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) | ||
| >y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) | ||
|
|
||
| const qux = (((x?.y ?? `oops`))) ?? ""; | ||
| >qux : Symbol(qux, Decl(neverNullishThroughParentheses.ts, 10, 5)) | ||
| >x?.y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) | ||
| >x : Symbol(x, Decl(neverNullishThroughParentheses.ts, 2, 5)) | ||
| >y : Symbol(y, Decl(neverNullishThroughParentheses.ts, 2, 10)) | ||
|
|
||
| // Test with different types | ||
| const str1 = ("literal") ?? "fallback"; | ||
| >str1 : Symbol(str1, Decl(neverNullishThroughParentheses.ts, 13, 5)) | ||
|
|
||
| const str2 = (("nested")) ?? "fallback"; | ||
| >str2 : Symbol(str2, Decl(neverNullishThroughParentheses.ts, 14, 5)) | ||
|
|
||
| const nested = ("a" ?? "b") ?? "c"; | ||
| >nested : Symbol(nested, Decl(neverNullishThroughParentheses.ts, 15, 5)) | ||
|
|
144 changes: 144 additions & 0 deletions
144
tests/baselines/reference/neverNullishThroughParentheses.types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| //// [tests/cases/compiler/neverNullishThroughParentheses.ts] //// | ||
|
|
||
| === neverNullishThroughParentheses.ts === | ||
| // Repro for issue where "never nullish" checks miss "never nullish" through parentheses | ||
|
|
||
| const x: { y: string | undefined } | undefined = undefined as any; | ||
| >x : { y: string | undefined; } | undefined | ||
| > : ^^^^^ ^^^^^^^^^^^^^^^ | ||
| >y : string | undefined | ||
| > : ^^^^^^^^^^^^^^^^^^ | ||
| >undefined as any : any | ||
| > : ^^^ | ||
| >undefined : undefined | ||
| > : ^^^^^^^^^ | ||
|
|
||
| // Both should error - both expressions are guaranteed to be "oops" | ||
| const foo = x?.y ?? `oops` ?? ""; | ||
| >foo : string | ||
| > : ^^^^^^ | ||
| >x?.y ?? `oops` ?? "" : string | ||
| > : ^^^^^^ | ||
| >x?.y ?? `oops` : string | ||
| > : ^^^^^^ | ||
| >x?.y : string | undefined | ||
| > : ^^^^^^^^^^^^^^^^^^ | ||
| >x : { y: string | undefined; } | undefined | ||
| > : ^^^^^ ^^^^^^^^^^^^^^^ | ||
| >y : string | undefined | ||
| > : ^^^^^^^^^^^^^^^^^^ | ||
| >`oops` : "oops" | ||
| > : ^^^^^^ | ||
| >"" : "" | ||
| > : ^^ | ||
|
|
||
| const bar = (x?.y ?? `oops`) ?? ""; | ||
| >bar : string | ||
| > : ^^^^^^ | ||
| >(x?.y ?? `oops`) ?? "" : string | ||
| > : ^^^^^^ | ||
| >(x?.y ?? `oops`) : string | ||
| > : ^^^^^^ | ||
| >x?.y ?? `oops` : string | ||
| > : ^^^^^^ | ||
| >x?.y : string | undefined | ||
| > : ^^^^^^^^^^^^^^^^^^ | ||
| >x : { y: string | undefined; } | undefined | ||
| > : ^^^^^ ^^^^^^^^^^^^^^^ | ||
| >y : string | undefined | ||
| > : ^^^^^^^^^^^^^^^^^^ | ||
| >`oops` : "oops" | ||
| > : ^^^^^^ | ||
| >"" : "" | ||
| > : ^^ | ||
|
|
||
| // Additional test cases with various levels of nesting | ||
| const baz = ((x?.y ?? `oops`)) ?? ""; | ||
| >baz : string | ||
| > : ^^^^^^ | ||
| >((x?.y ?? `oops`)) ?? "" : string | ||
| > : ^^^^^^ | ||
| >((x?.y ?? `oops`)) : string | ||
| > : ^^^^^^ | ||
| >(x?.y ?? `oops`) : string | ||
| > : ^^^^^^ | ||
| >x?.y ?? `oops` : string | ||
| > : ^^^^^^ | ||
| >x?.y : string | undefined | ||
| > : ^^^^^^^^^^^^^^^^^^ | ||
| >x : { y: string | undefined; } | undefined | ||
| > : ^^^^^ ^^^^^^^^^^^^^^^ | ||
| >y : string | undefined | ||
| > : ^^^^^^^^^^^^^^^^^^ | ||
| >`oops` : "oops" | ||
| > : ^^^^^^ | ||
| >"" : "" | ||
| > : ^^ | ||
|
|
||
| const qux = (((x?.y ?? `oops`))) ?? ""; | ||
| >qux : string | ||
| > : ^^^^^^ | ||
| >(((x?.y ?? `oops`))) ?? "" : string | ||
| > : ^^^^^^ | ||
| >(((x?.y ?? `oops`))) : string | ||
| > : ^^^^^^ | ||
| >((x?.y ?? `oops`)) : string | ||
| > : ^^^^^^ | ||
| >(x?.y ?? `oops`) : string | ||
| > : ^^^^^^ | ||
| >x?.y ?? `oops` : string | ||
| > : ^^^^^^ | ||
| >x?.y : string | undefined | ||
| > : ^^^^^^^^^^^^^^^^^^ | ||
| >x : { y: string | undefined; } | undefined | ||
| > : ^^^^^ ^^^^^^^^^^^^^^^ | ||
| >y : string | undefined | ||
| > : ^^^^^^^^^^^^^^^^^^ | ||
| >`oops` : "oops" | ||
| > : ^^^^^^ | ||
| >"" : "" | ||
| > : ^^ | ||
|
|
||
| // Test with different types | ||
| const str1 = ("literal") ?? "fallback"; | ||
| >str1 : "literal" | ||
| > : ^^^^^^^^^ | ||
| >("literal") ?? "fallback" : "literal" | ||
| > : ^^^^^^^^^ | ||
| >("literal") : "literal" | ||
| > : ^^^^^^^^^ | ||
| >"literal" : "literal" | ||
| > : ^^^^^^^^^ | ||
| >"fallback" : "fallback" | ||
| > : ^^^^^^^^^^ | ||
|
|
||
| const str2 = (("nested")) ?? "fallback"; | ||
| >str2 : "nested" | ||
| > : ^^^^^^^^ | ||
| >(("nested")) ?? "fallback" : "nested" | ||
| > : ^^^^^^^^ | ||
| >(("nested")) : "nested" | ||
| > : ^^^^^^^^ | ||
| >("nested") : "nested" | ||
| > : ^^^^^^^^ | ||
| >"nested" : "nested" | ||
| > : ^^^^^^^^ | ||
| >"fallback" : "fallback" | ||
| > : ^^^^^^^^^^ | ||
|
|
||
| const nested = ("a" ?? "b") ?? "c"; | ||
| >nested : "a" | ||
| > : ^^^ | ||
| >("a" ?? "b") ?? "c" : "a" | ||
| > : ^^^ | ||
| >("a" ?? "b") : "a" | ||
| > : ^^^ | ||
| >"a" ?? "b" : "a" | ||
| > : ^^^ | ||
| >"a" : "a" | ||
| > : ^^^ | ||
| >"b" : "b" | ||
| > : ^^^ | ||
| >"c" : "c" | ||
| > : ^^^ | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // @strict: true | ||
|
|
||
| // Repro for issue where "never nullish" checks miss "never nullish" through parentheses | ||
|
|
||
| const x: { y: string | undefined } | undefined = undefined as any; | ||
|
|
||
| // Both should error - both expressions are guaranteed to be "oops" | ||
| const foo = x?.y ?? `oops` ?? ""; | ||
| const bar = (x?.y ?? `oops`) ?? ""; | ||
|
|
||
| // Additional test cases with various levels of nesting | ||
| const baz = ((x?.y ?? `oops`)) ?? ""; | ||
| const qux = (((x?.y ?? `oops`))) ?? ""; | ||
|
|
||
| // Test with different types | ||
| const str1 = ("literal") ?? "fallback"; | ||
| const str2 = (("nested")) ?? "fallback"; | ||
| const nested = ("a" ?? "b") ?? "c"; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.