diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 67a733cbbb811..f905000abf8ac 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -39846,18 +39846,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkNullishCoalesceOperands(node: BinaryExpression) { - const { left, operatorToken, right } = node; - if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) { - if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { - grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind)); - } - if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { - grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind)); + if (node.operatorToken.kind !== SyntaxKind.QuestionQuestionToken) { + return; + } + if (isBinaryExpression(node.parent)) { + const { left, operatorToken } = node.parent; + if (isBinaryExpression(left) && (operatorToken.kind === SyntaxKind.BarBarToken || operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { + grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(SyntaxKind.QuestionQuestionToken), tokenToString(operatorToken.kind)); } - - checkNullishCoalesceOperandLeft(node); - checkNullishCoalesceOperandRight(node); } + else if (isBinaryExpression(node.left) && node.left.operatorToken.kind === SyntaxKind.BarBarToken) { + grammarErrorOnNode(node.left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(node.left.operatorToken.kind), tokenToString(SyntaxKind.QuestionQuestionToken)); + } + else if (isBinaryExpression(node.right) && node.right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + grammarErrorOnNode(node.right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(SyntaxKind.QuestionQuestionToken), tokenToString(node.right.operatorToken.kind)); + } + checkNullishCoalesceOperandLeft(node); + checkNullishCoalesceOperandRight(node); } function checkNullishCoalesceOperandLeft(node: BinaryExpression) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5755369134d8a..391765cab7595 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5658,17 +5658,17 @@ export const enum OperatorPrecedence { // CoalesceExpression Conditional, + // LogicalORExpression: + // LogicalANDExpression + // LogicalORExpression `||` LogicalANDExpression + LogicalOR, + // CoalesceExpression: // CoalesceExpressionHead `??` BitwiseORExpression // CoalesceExpressionHead: // CoalesceExpression // BitwiseORExpression - Coalesce = Conditional, // NOTE: This is wrong - - // LogicalORExpression: - // LogicalANDExpression - // LogicalORExpression `||` LogicalANDExpression - LogicalOR, + Coalesce = LogicalOR, // LogicalANDExpression: // BitwiseORExpression diff --git a/src/testRunner/unittests/printer.ts b/src/testRunner/unittests/printer.ts index cff0ce2e47f3e..ab6dd5a718415 100644 --- a/src/testRunner/unittests/printer.ts +++ b/src/testRunner/unittests/printer.ts @@ -366,5 +366,62 @@ describe("unittests:: PrinterAPI", () => { ts.factory.createNoSubstitutionTemplateLiteral("\n"), ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext), )); + + printsCorrectly("binaryBarBarExpressionWithLeftConditionalExpression", {}, printer => + printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createExpressionStatement( + ts.factory.createBinaryExpression( + ts.factory.createConditionalExpression( + ts.factory.createIdentifier("a"), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createIdentifier("b"), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + ts.factory.createIdentifier("c"), + ), + ts.factory.createToken(ts.SyntaxKind.BarBarToken), + ts.factory.createIdentifier("d"), + ), + ), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext), + )); + + printsCorrectly("binaryAmpersandAmpersandExpressionWithLeftConditionalExpression", {}, printer => + printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createExpressionStatement( + ts.factory.createBinaryExpression( + ts.factory.createConditionalExpression( + ts.factory.createIdentifier("a"), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createIdentifier("b"), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + ts.factory.createIdentifier("c"), + ), + ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), + ts.factory.createIdentifier("d"), + ), + ), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext), + )); + + printsCorrectly("binaryQuestionQuestionExpressionWithLeftConditionalExpression", {}, printer => + printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createExpressionStatement( + ts.factory.createBinaryExpression( + ts.factory.createConditionalExpression( + ts.factory.createIdentifier("a"), + ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createIdentifier("b"), + ts.factory.createToken(ts.SyntaxKind.ColonToken), + ts.factory.createIdentifier("c"), + ), + ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), + ts.factory.createIdentifier("d"), + ), + ), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext), + )); }); }); diff --git a/tests/baselines/reference/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.js b/tests/baselines/reference/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.js new file mode 100644 index 0000000000000..27c295f342667 --- /dev/null +++ b/tests/baselines/reference/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.js @@ -0,0 +1,45 @@ +//// [tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts] //// + +//// [nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts] +// https://github.com/microsoft/TypeScript/issues/61109 + +class Cls { + #privateProp: number | undefined; + + problem() { + this.#privateProp ??= false ? neverThis() : 20; + } +} + +function neverThis(): never { + throw new Error("This should really really never happen!"); +} + + +//// [nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.js] +"use strict"; +// https://github.com/microsoft/TypeScript/issues/61109 +var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); + return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); +}; +var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { + if (kind === "m") throw new TypeError("Private method is not writable"); + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); + return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; +}; +var _Cls_privateProp; +class Cls { + constructor() { + _Cls_privateProp.set(this, void 0); + } + problem() { + __classPrivateFieldSet(this, _Cls_privateProp, __classPrivateFieldGet(this, _Cls_privateProp, "f") ?? (false ? neverThis() : 20), "f"); + } +} +_Cls_privateProp = new WeakMap(); +function neverThis() { + throw new Error("This should really really never happen!"); +} diff --git a/tests/baselines/reference/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.symbols b/tests/baselines/reference/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.symbols new file mode 100644 index 0000000000000..74f712c9c2cfb --- /dev/null +++ b/tests/baselines/reference/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.symbols @@ -0,0 +1,28 @@ +//// [tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts] //// + +=== nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts === +// https://github.com/microsoft/TypeScript/issues/61109 + +class Cls { +>Cls : Symbol(Cls, Decl(nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts, 0, 0)) + + #privateProp: number | undefined; +>#privateProp : Symbol(Cls.#privateProp, Decl(nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts, 2, 11)) + + problem() { +>problem : Symbol(Cls.problem, Decl(nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts, 3, 35)) + + this.#privateProp ??= false ? neverThis() : 20; +>this.#privateProp : Symbol(Cls.#privateProp, Decl(nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts, 2, 11)) +>this : Symbol(Cls, Decl(nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts, 0, 0)) +>neverThis : Symbol(neverThis, Decl(nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts, 8, 1)) + } +} + +function neverThis(): never { +>neverThis : Symbol(neverThis, Decl(nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts, 8, 1)) + + throw new Error("This should really really never happen!"); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.types b/tests/baselines/reference/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.types new file mode 100644 index 0000000000000..6c2c83ecfc35b --- /dev/null +++ b/tests/baselines/reference/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.types @@ -0,0 +1,50 @@ +//// [tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts] //// + +=== nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts === +// https://github.com/microsoft/TypeScript/issues/61109 + +class Cls { +>Cls : Cls +> : ^^^ + + #privateProp: number | undefined; +>#privateProp : number | undefined +> : ^^^^^^^^^^^^^^^^^^ + + problem() { +>problem : () => void +> : ^^^^^^^^^^ + + this.#privateProp ??= false ? neverThis() : 20; +>this.#privateProp ??= false ? neverThis() : 20 : number +> : ^^^^^^ +>this.#privateProp : number | undefined +> : ^^^^^^^^^^^^^^^^^^ +>this : this +> : ^^^^ +>false ? neverThis() : 20 : 20 +> : ^^ +>false : false +> : ^^^^^ +>neverThis() : never +> : ^^^^^ +>neverThis : () => never +> : ^^^^^^ +>20 : 20 +> : ^^ + } +} + +function neverThis(): never { +>neverThis : () => never +> : ^^^^^^ + + throw new Error("This should really really never happen!"); +>new Error("This should really really never happen!") : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +>"This should really really never happen!" : "This should really really never happen!" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +} + diff --git a/tests/baselines/reference/nullishCoalescingOperator5.errors.txt b/tests/baselines/reference/nullishCoalescingOperator5.errors.txt index a352e2b340b24..6ebb00e3ba578 100644 --- a/tests/baselines/reference/nullishCoalescingOperator5.errors.txt +++ b/tests/baselines/reference/nullishCoalescingOperator5.errors.txt @@ -1,18 +1,17 @@ -nullishCoalescingOperator5.ts(6,6): error TS5076: '||' and '??' operations cannot be mixed without parentheses. +nullishCoalescingOperator5.ts(6,1): error TS5076: '??' and '||' operations cannot be mixed without parentheses. nullishCoalescingOperator5.ts(9,1): error TS5076: '||' and '??' operations cannot be mixed without parentheses. -nullishCoalescingOperator5.ts(12,6): error TS5076: '&&' and '??' operations cannot be mixed without parentheses. -nullishCoalescingOperator5.ts(15,1): error TS5076: '&&' and '??' operations cannot be mixed without parentheses. +nullishCoalescingOperator5.ts(12,6): error TS5076: '??' and '&&' operations cannot be mixed without parentheses. -==== nullishCoalescingOperator5.ts (4 errors) ==== +==== nullishCoalescingOperator5.ts (3 errors) ==== declare const a: string | undefined declare const b: string | undefined declare const c: string | undefined // should be a syntax error a ?? b || c; - ~~~~~~ -!!! error TS5076: '||' and '??' operations cannot be mixed without parentheses. + ~~~~~~ +!!! error TS5076: '??' and '||' operations cannot be mixed without parentheses. // should be a syntax error a || b ?? c; @@ -22,12 +21,10 @@ nullishCoalescingOperator5.ts(15,1): error TS5076: '&&' and '??' operations cann // should be a syntax error a ?? b && c; ~~~~~~ -!!! error TS5076: '&&' and '??' operations cannot be mixed without parentheses. +!!! error TS5076: '??' and '&&' operations cannot be mixed without parentheses. // should be a syntax error a && b ?? c; - ~~~~~~ -!!! error TS5076: '&&' and '??' operations cannot be mixed without parentheses. // Valid according to spec a ?? (b || c); diff --git a/tests/baselines/reference/nullishCoalescingOperator5.js b/tests/baselines/reference/nullishCoalescingOperator5.js index c62722ca6342e..504bd34d6bd06 100644 --- a/tests/baselines/reference/nullishCoalescingOperator5.js +++ b/tests/baselines/reference/nullishCoalescingOperator5.js @@ -46,7 +46,7 @@ a && (b ?? c); "use strict"; var _a, _b, _c, _d; // should be a syntax error -a !== null && a !== void 0 ? a : b || c; +(a !== null && a !== void 0 ? a : b) || c; // should be a syntax error (_a = a || b) !== null && _a !== void 0 ? _a : c; // should be a syntax error diff --git a/tests/baselines/reference/nullishCoalescingOperator5.types b/tests/baselines/reference/nullishCoalescingOperator5.types index 77f4256270182..60cf7844a2f4a 100644 --- a/tests/baselines/reference/nullishCoalescingOperator5.types +++ b/tests/baselines/reference/nullishCoalescingOperator5.types @@ -17,10 +17,10 @@ declare const c: string | undefined a ?? b || c; >a ?? b || c : string | undefined > : ^^^^^^^^^^^^^^^^^^ +>a ?? b : string | undefined +> : ^^^^^^^^^^^^^^^^^^ >a : string | undefined > : ^^^^^^^^^^^^^^^^^^ ->b || c : string | undefined -> : ^^^^^^^^^^^^^^^^^^ >b : string | undefined > : ^^^^^^^^^^^^^^^^^^ >c : string | undefined diff --git a/tests/baselines/reference/plainJSGrammarErrors.errors.txt b/tests/baselines/reference/plainJSGrammarErrors.errors.txt index b92bd5df1b8ba..3e84d9cf926ad 100644 --- a/tests/baselines/reference/plainJSGrammarErrors.errors.txt +++ b/tests/baselines/reference/plainJSGrammarErrors.errors.txt @@ -59,7 +59,7 @@ plainJSGrammarErrors.js(92,33): error TS2566: A rest element cannot have a prope plainJSGrammarErrors.js(93,42): error TS1186: A rest element cannot have an initializer. plainJSGrammarErrors.js(96,4): error TS1123: Variable declaration list cannot be empty. plainJSGrammarErrors.js(97,9): error TS5076: '||' and '??' operations cannot be mixed without parentheses. -plainJSGrammarErrors.js(98,14): error TS5076: '||' and '??' operations cannot be mixed without parentheses. +plainJSGrammarErrors.js(98,9): error TS5076: '??' and '||' operations cannot be mixed without parentheses. plainJSGrammarErrors.js(100,3): error TS1200: Line terminator not permitted before arrow. plainJSGrammarErrors.js(102,4): error TS1358: Tagged template expressions are not permitted in an optional chain. plainJSGrammarErrors.js(104,6): error TS1171: A comma expression is not allowed in a computed property name. @@ -323,8 +323,8 @@ plainJSGrammarErrors.js(205,36): error TS1325: Argument of dynamic import cannot ~~~~~~ !!! error TS5076: '||' and '??' operations cannot be mixed without parentheses. var x = 2 ?? 3 || 4 - ~~~~~~ -!!! error TS5076: '||' and '??' operations cannot be mixed without parentheses. + ~~~~~~ +!!! error TS5076: '??' and '||' operations cannot be mixed without parentheses. const arr = x => x + 1 ~~ diff --git a/tests/baselines/reference/plainJSGrammarErrors.types b/tests/baselines/reference/plainJSGrammarErrors.types index 468fbb05e5610..553d4553bd8ad 100644 --- a/tests/baselines/reference/plainJSGrammarErrors.types +++ b/tests/baselines/reference/plainJSGrammarErrors.types @@ -421,10 +421,10 @@ var x = 2 ?? 3 || 4 > : ^^^^^^ >2 ?? 3 || 4 : 2 | 3 | 4 > : ^^^^^^^^^ +>2 ?? 3 : 2 | 3 +> : ^^^^^ >2 : 2 > : ^ ->3 || 4 : 3 | 4 -> : ^^^^^ >3 : 3 > : ^ >4 : 4 diff --git a/tests/baselines/reference/printerApi/printsNodeCorrectly.binaryAmpersandAmpersandExpressionWithLeftConditionalExpression.js b/tests/baselines/reference/printerApi/printsNodeCorrectly.binaryAmpersandAmpersandExpressionWithLeftConditionalExpression.js new file mode 100644 index 0000000000000..fdabe98ff68b0 --- /dev/null +++ b/tests/baselines/reference/printerApi/printsNodeCorrectly.binaryAmpersandAmpersandExpressionWithLeftConditionalExpression.js @@ -0,0 +1 @@ +(a ? b : c) && d; \ No newline at end of file diff --git a/tests/baselines/reference/printerApi/printsNodeCorrectly.binaryBarBarExpressionWithLeftConditionalExpression.js b/tests/baselines/reference/printerApi/printsNodeCorrectly.binaryBarBarExpressionWithLeftConditionalExpression.js new file mode 100644 index 0000000000000..eabe4414aea98 --- /dev/null +++ b/tests/baselines/reference/printerApi/printsNodeCorrectly.binaryBarBarExpressionWithLeftConditionalExpression.js @@ -0,0 +1 @@ +(a ? b : c) || d; \ No newline at end of file diff --git a/tests/baselines/reference/printerApi/printsNodeCorrectly.binaryQuestionQuestionExpressionWithLeftConditionalExpression.js b/tests/baselines/reference/printerApi/printsNodeCorrectly.binaryQuestionQuestionExpressionWithLeftConditionalExpression.js new file mode 100644 index 0000000000000..81c6e8b75db55 --- /dev/null +++ b/tests/baselines/reference/printerApi/printsNodeCorrectly.binaryQuestionQuestionExpressionWithLeftConditionalExpression.js @@ -0,0 +1 @@ +(a ? b : c) ?? d; \ No newline at end of file diff --git a/tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts b/tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts new file mode 100644 index 0000000000000..780076992fbab --- /dev/null +++ b/tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingAssignmentVsPrivateFieldsJsEmit1.ts @@ -0,0 +1,16 @@ +// @strict: true +// @target: es2021 + +// https://github.com/microsoft/TypeScript/issues/61109 + +class Cls { + #privateProp: number | undefined; + + problem() { + this.#privateProp ??= false ? neverThis() : 20; + } +} + +function neverThis(): never { + throw new Error("This should really really never happen!"); +}