mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 03:09:39 -06:00
Fixed nullish coalesce operator's precedence (#61372)
This commit is contained in:
parent
58665cf3ae
commit
91cdbd518c
@ -40472,18 +40472,29 @@ 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));
|
||||
}
|
||||
|
||||
checkNullishCoalesceOperandLeft(node);
|
||||
checkNullishCoalesceOperandRight(node);
|
||||
if (node.operatorToken.kind !== SyntaxKind.QuestionQuestionToken) {
|
||||
return;
|
||||
}
|
||||
if (isBinaryExpression(node.parent)) {
|
||||
const { left, operatorToken } = node.parent;
|
||||
if (isBinaryExpression(left) && operatorToken.kind === SyntaxKind.BarBarToken) {
|
||||
grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(SyntaxKind.QuestionQuestionToken), tokenToString(operatorToken.kind));
|
||||
}
|
||||
}
|
||||
else if (isBinaryExpression(node.left)) {
|
||||
const { operatorToken } = node.left;
|
||||
if (operatorToken.kind === SyntaxKind.BarBarToken || operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
|
||||
grammarErrorOnNode(node.left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(operatorToken.kind), tokenToString(SyntaxKind.QuestionQuestionToken));
|
||||
}
|
||||
}
|
||||
else if (isBinaryExpression(node.right)) {
|
||||
const { operatorToken } = node.right;
|
||||
if (operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
|
||||
grammarErrorOnNode(node.right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(SyntaxKind.QuestionQuestionToken), tokenToString(operatorToken.kind));
|
||||
}
|
||||
}
|
||||
checkNullishCoalesceOperandLeft(node);
|
||||
checkNullishCoalesceOperandRight(node);
|
||||
}
|
||||
|
||||
function checkNullishCoalesceOperandLeft(node: BinaryExpression) {
|
||||
|
||||
@ -5675,17 +5675,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
|
||||
|
||||
@ -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),
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
@ -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!");
|
||||
}
|
||||
@ -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, --, --))
|
||||
}
|
||||
|
||||
@ -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!"
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
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(12,6): error TS5076: '??' and '&&' operations cannot be mixed without parentheses.
|
||||
nullishCoalescingOperator5.ts(15,1): error TS5076: '&&' and '??' operations cannot be mixed without parentheses.
|
||||
|
||||
|
||||
@ -11,8 +11,8 @@ 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;
|
||||
@ -22,7 +22,7 @@ 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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
~~
|
||||
|
||||
@ -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
|
||||
|
||||
@ -0,0 +1 @@
|
||||
(a ? b : c) && d;
|
||||
@ -0,0 +1 @@
|
||||
(a ? b : c) || d;
|
||||
@ -0,0 +1 @@
|
||||
(a ? b : c) ?? d;
|
||||
@ -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!");
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user