From 9636938d64084f2d6b38cd2d7978856f9a52f7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 30 Jan 2023 09:53:18 +0100 Subject: [PATCH 1/4] Widen widening literal types through compound-like assignments --- src/compiler/checker.ts | 15 ++- src/compiler/factory/utilities.ts | 3 +- src/compiler/utilities.ts | 11 +- ...ideningWithCompoundLikeAssignments.symbols | 101 ++++++++++++++ ...lWideningWithCompoundLikeAssignments.types | 126 ++++++++++++++++++ ...eralWideningWithCompoundLikeAssignments.ts | 41 ++++++ 6 files changed, 289 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/literalWideningWithCompoundLikeAssignments.symbols create mode 100644 tests/baselines/reference/literalWideningWithCompoundLikeAssignments.types create mode 100644 tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ca37ad9acf807..ed7f19f5adf59 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26208,7 +26208,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!isReachableFlowNode(flow)) { return unreachableNeverType; } - if (getAssignmentTargetKind(node) === AssignmentKind.Compound) { + const assignmentKind = getAssignmentTargetKind(node); + if (assignmentKind === AssignmentKind.Compound) { const flowType = getTypeAtFlowNode(flow.antecedent); return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); } @@ -26219,10 +26220,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow)); return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; } - if (declaredType.flags & TypeFlags.Union) { - return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow)); + const t = assignmentKind === AssignmentKind.CompoundLike ? getBaseTypeOfLiteralType(declaredType) : declaredType; + if (t.flags & TypeFlags.Union) { + return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow)); } - return declaredType; + return t; } // We didn't have a direct match. However, if the reference is a dotted name, this // may be an assignment to a left hand part of the reference. For example, for a @@ -27570,6 +27572,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (assignmentKind === AssignmentKind.Definite) { return type; } + if (assignmentKind === AssignmentKind.CompoundLike) { + return getBaseTypeOfLiteralType(type); + } } else if (isAlias) { declaration = getDeclarationOfAliasSymbol(symbol); @@ -31090,7 +31095,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // assignment target, and the referenced property was declared as a variable, property, // accessor, or optional method. const assignmentKind = getAssignmentTargetKind(node); - if (assignmentKind === AssignmentKind.Definite) { + if (assignmentKind === AssignmentKind.Definite || assignmentKind === AssignmentKind.CompoundLike) { return removeMissingType(propType, !!(prop && prop.flags & SymbolFlags.Optional)); } if (prop && diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index 61cbc7dffc7f5..f0f564b315afa 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -1206,7 +1206,8 @@ function isAdditiveOperator(kind: SyntaxKind): kind is AdditiveOperator { || kind === SyntaxKind.MinusToken; } -function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher { +/** @internal */ +export function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher { return isAdditiveOperator(kind) || isMultiplicativeOperatorOrHigher(kind); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 57c7bf9aa8329..4b39ec11f4859 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -230,6 +230,7 @@ import { InterfaceDeclaration, InternalEmitFlags, isAccessor, + isAdditiveOperatorOrHigher, isAnyDirectorySeparator, isArray, isArrayLiteralExpression, @@ -4009,7 +4010,7 @@ export function hasTypeArguments(node: Node): node is HasTypeArguments { /** @internal */ export const enum AssignmentKind { - None, Definite, Compound + None, Definite, Compound, CompoundLike } /** @internal */ @@ -4020,7 +4021,8 @@ export function getAssignmentTargetKind(node: Node): AssignmentKind { case SyntaxKind.BinaryExpression: const binaryOperator = (parent as BinaryExpression).operatorToken.kind; return isAssignmentOperator(binaryOperator) && (parent as BinaryExpression).left === node ? - binaryOperator === SyntaxKind.EqualsToken || isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? AssignmentKind.Definite : AssignmentKind.Compound : + binaryOperator === SyntaxKind.EqualsToken ? (isCompoundLikeAssignment(parent as BinaryExpression) ? AssignmentKind.CompoundLike : AssignmentKind.Definite) : + isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? AssignmentKind.Definite : AssignmentKind.Compound : AssignmentKind.None; case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: @@ -4066,6 +4068,11 @@ export function isAssignmentTarget(node: Node): boolean { return getAssignmentTargetKind(node) !== AssignmentKind.None; } +function isCompoundLikeAssignment(assignment: BinaryExpression): boolean { + const right = skipParentheses(assignment.right); + return right.kind === SyntaxKind.BinaryExpression && isAdditiveOperatorOrHigher((right as BinaryExpression).operatorToken.kind); +} + /** @internal */ export type NodeWithPossibleHoistedDeclaration = | Block diff --git a/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.symbols b/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.symbols new file mode 100644 index 0000000000000..8de35124b6fac --- /dev/null +++ b/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.symbols @@ -0,0 +1,101 @@ +=== tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts === +// repro from #13865 + +const empty: "" = ""; +>empty : Symbol(empty, Decl(literalWideningWithCompoundLikeAssignments.ts, 2, 5)) + +let foo = empty; +>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3)) +>empty : Symbol(empty, Decl(literalWideningWithCompoundLikeAssignments.ts, 2, 5)) + +foo = foo + "bar" +>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3)) +>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3)) + +foo // string +>foo : Symbol(foo, Decl(literalWideningWithCompoundLikeAssignments.ts, 3, 3)) + +declare const numLiteral: 0; +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +let t1 = numLiteral; +>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3)) +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +t1 = t1 + 42 +>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3)) +>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3)) + +t1 // number +>t1 : Symbol(t1, Decl(literalWideningWithCompoundLikeAssignments.ts, 9, 3)) + +let t2 = numLiteral; +>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3)) +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +t2 = t2 - 42 +>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3)) +>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3)) + +t2 // number +>t2 : Symbol(t2, Decl(literalWideningWithCompoundLikeAssignments.ts, 13, 3)) + +let t3 = numLiteral; +>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3)) +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +t3 = t3 * 42 +>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3)) +>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3)) + +t3 // number +>t3 : Symbol(t3, Decl(literalWideningWithCompoundLikeAssignments.ts, 17, 3)) + +let t4 = numLiteral; +>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3)) +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +t4 = t4 ** 42 +>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3)) +>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3)) + +t4 // number +>t4 : Symbol(t4, Decl(literalWideningWithCompoundLikeAssignments.ts, 21, 3)) + +let t5 = numLiteral; +>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3)) +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +t5 = t5 / 42 +>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3)) +>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3)) + +t5 // number +>t5 : Symbol(t5, Decl(literalWideningWithCompoundLikeAssignments.ts, 25, 3)) + +let t6 = numLiteral; +>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3)) +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +t6 = t6 % 42 +>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3)) +>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3)) + +t6 // number +>t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3)) + + +declare const literalUnion: "a" | 0; +>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 34, 13)) + +let t7 = literalUnion; +>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3)) +>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 34, 13)) + +t7 = t7 + 'b' +>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3)) +>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3)) + +t7 // string +>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3)) + diff --git a/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.types b/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.types new file mode 100644 index 0000000000000..541c770771585 --- /dev/null +++ b/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.types @@ -0,0 +1,126 @@ +=== tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts === +// repro from #13865 + +const empty: "" = ""; +>empty : "" +>"" : "" + +let foo = empty; +>foo : "" +>empty : "" + +foo = foo + "bar" +>foo = foo + "bar" : string +>foo : string +>foo + "bar" : string +>foo : "" +>"bar" : "bar" + +foo // string +>foo : string + +declare const numLiteral: 0; +>numLiteral : 0 + +let t1 = numLiteral; +>t1 : 0 +>numLiteral : 0 + +t1 = t1 + 42 +>t1 = t1 + 42 : number +>t1 : number +>t1 + 42 : number +>t1 : 0 +>42 : 42 + +t1 // number +>t1 : number + +let t2 = numLiteral; +>t2 : 0 +>numLiteral : 0 + +t2 = t2 - 42 +>t2 = t2 - 42 : number +>t2 : number +>t2 - 42 : number +>t2 : 0 +>42 : 42 + +t2 // number +>t2 : number + +let t3 = numLiteral; +>t3 : 0 +>numLiteral : 0 + +t3 = t3 * 42 +>t3 = t3 * 42 : number +>t3 : number +>t3 * 42 : number +>t3 : 0 +>42 : 42 + +t3 // number +>t3 : number + +let t4 = numLiteral; +>t4 : 0 +>numLiteral : 0 + +t4 = t4 ** 42 +>t4 = t4 ** 42 : number +>t4 : number +>t4 ** 42 : number +>t4 : 0 +>42 : 42 + +t4 // number +>t4 : number + +let t5 = numLiteral; +>t5 : 0 +>numLiteral : 0 + +t5 = t5 / 42 +>t5 = t5 / 42 : number +>t5 : number +>t5 / 42 : number +>t5 : 0 +>42 : 42 + +t5 // number +>t5 : number + +let t6 = numLiteral; +>t6 : 0 +>numLiteral : 0 + +t6 = t6 % 42 +>t6 = t6 % 42 : number +>t6 : number +>t6 % 42 : number +>t6 : 0 +>42 : 42 + +t6 // number +>t6 : number + + +declare const literalUnion: "a" | 0; +>literalUnion : 0 | "a" + +let t7 = literalUnion; +>t7 : 0 | "a" +>literalUnion : 0 | "a" + +t7 = t7 + 'b' +>t7 = t7 + 'b' : string +>t7 : string | number +>t7 + 'b' : string +>t7 : 0 | "a" +>'b' : "b" + +t7 // string +>t7 : string + diff --git a/tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts b/tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts new file mode 100644 index 0000000000000..da4a7fb0af86b --- /dev/null +++ b/tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts @@ -0,0 +1,41 @@ +// @strict: true +// @noEmit: true + +// repro from #13865 + +const empty: "" = ""; +let foo = empty; +foo = foo + "bar" +foo // string + +declare const numLiteral: 0; + +let t1 = numLiteral; +t1 = t1 + 42 +t1 // number + +let t2 = numLiteral; +t2 = t2 - 42 +t2 // number + +let t3 = numLiteral; +t3 = t3 * 42 +t3 // number + +let t4 = numLiteral; +t4 = t4 ** 42 +t4 // number + +let t5 = numLiteral; +t5 = t5 / 42 +t5 // number + +let t6 = numLiteral; +t6 = t6 % 42 +t6 // number + + +declare const literalUnion: "a" | 0; +let t7 = literalUnion; +t7 = t7 + 'b' +t7 // string From f789491beeaa71a7e790fd7f7e3262cae58e323b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 30 Jan 2023 10:44:32 +0100 Subject: [PATCH 2/4] Fixed a case around setonly accesstors --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ed7f19f5adf59..010c385bd8323 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30986,7 +30986,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else { const isSetonlyAccessor = prop && prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) { + if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite && assignmentKind !== AssignmentKind.CompoundLike) { error(node, Diagnostics.Private_accessor_was_defined_without_a_getter); } } From e09ec9542c2e296bad38ab460890d2a221c77ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 26 Jun 2023 23:53:40 +0200 Subject: [PATCH 3/4] Switch to a separate util --- src/compiler/checker.ts | 15 +++----- src/compiler/utilities.ts | 77 +++++++++++++++++++++++++++------------ 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5223d1865fae5..105c4fa39e7b2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -552,6 +552,7 @@ import { isImportOrExportSpecifier, isImportSpecifier, isImportTypeNode, + isInCompoundLikeAssignment, isIndexedAccessTypeNode, isInExpressionContext, isInfinityOrNaNString, @@ -26662,8 +26663,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!isReachableFlowNode(flow)) { return unreachableNeverType; } - const assignmentKind = getAssignmentTargetKind(node); - if (assignmentKind === AssignmentKind.Compound) { + if (getAssignmentTargetKind(node) === AssignmentKind.Compound) { const flowType = getTypeAtFlowNode(flow.antecedent); return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); } @@ -26674,7 +26674,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow)); return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; } - const t = assignmentKind === AssignmentKind.CompoundLike ? getBaseTypeOfLiteralType(declaredType) : declaredType; + const t = isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(declaredType) : declaredType; if (t.flags & TypeFlags.Union) { return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow)); } @@ -28037,10 +28037,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // entities we simply return the declared type. if (localOrExportSymbol.flags & SymbolFlags.Variable) { if (assignmentKind === AssignmentKind.Definite) { - return type; - } - if (assignmentKind === AssignmentKind.CompoundLike) { - return getBaseTypeOfLiteralType(type); + return isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type; } } else if (isAlias) { @@ -31576,7 +31573,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else { const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite && assignmentKind !== AssignmentKind.CompoundLike) { + if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) { error(node, Diagnostics.Private_accessor_was_defined_without_a_getter); } } @@ -31685,7 +31682,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // assignment target, and the referenced property was declared as a variable, property, // accessor, or optional method. const assignmentKind = getAssignmentTargetKind(node); - if (assignmentKind === AssignmentKind.Definite || assignmentKind === AssignmentKind.CompoundLike) { + if (assignmentKind === AssignmentKind.Definite) { return removeMissingType(propType, !!(prop && prop.flags & SymbolFlags.Optional)); } if (prop && diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ab50713d681de..2b8c9646bbcb5 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -152,8 +152,6 @@ import { forEachChild, forEachChildRecursively, ForInOrOfStatement, - ForInStatement, - ForOfStatement, ForStatement, FunctionBody, FunctionDeclaration, @@ -3417,9 +3415,9 @@ export function isInExpressionContext(node: Node): boolean { forStatement.incrementor === node; case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: - const forInStatement = parent as ForInStatement | ForOfStatement; - return (forInStatement.initializer === node && forInStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || - forInStatement.expression === node; + const forInOrOfStatement = parent as ForInOrOfStatement; + return (forInOrOfStatement.initializer === node && forInOrOfStatement.initializer.kind !== SyntaxKind.VariableDeclarationList) || + forInOrOfStatement.expression === node; case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: return node === (parent as AssertionExpression).expression; @@ -4464,27 +4462,32 @@ export function hasTypeArguments(node: Node): node is HasTypeArguments { /** @internal */ export const enum AssignmentKind { - None, Definite, Compound, CompoundLike + None, Definite, Compound } -/** @internal */ -export function getAssignmentTargetKind(node: Node): AssignmentKind { +type AssignmentTarget = + | BinaryExpression + | PrefixUnaryExpression + | PostfixUnaryExpression + | ForInOrOfStatement; + +function getAssignmentTarget(node: Node): AssignmentTarget | undefined { let parent = node.parent; while (true) { switch (parent.kind) { case SyntaxKind.BinaryExpression: - const binaryOperator = (parent as BinaryExpression).operatorToken.kind; - return isAssignmentOperator(binaryOperator) && (parent as BinaryExpression).left === node ? - binaryOperator === SyntaxKind.EqualsToken ? (isCompoundLikeAssignment(parent as BinaryExpression) ? AssignmentKind.CompoundLike : AssignmentKind.Definite) : - isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? AssignmentKind.Definite : AssignmentKind.Compound : - AssignmentKind.None; + const binaryExpression = parent as BinaryExpression; + const binaryOperator = binaryExpression.operatorToken.kind; + return isAssignmentOperator(binaryOperator) && binaryExpression.left === node ? binaryExpression : undefined; case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: - const unaryOperator = (parent as PrefixUnaryExpression | PostfixUnaryExpression).operator; - return unaryOperator === SyntaxKind.PlusPlusToken || unaryOperator === SyntaxKind.MinusMinusToken ? AssignmentKind.Compound : AssignmentKind.None; + const unaryExpression = (parent as PrefixUnaryExpression | PostfixUnaryExpression); + const unaryOperator = unaryExpression.operator; + return unaryOperator === SyntaxKind.PlusPlusToken || unaryOperator === SyntaxKind.MinusMinusToken ? unaryExpression : undefined; case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: - return (parent as ForInOrOfStatement).initializer === node ? AssignmentKind.Definite : AssignmentKind.None; + const forInOrOfStatement = parent as ForInOrOfStatement; + return forInOrOfStatement.initializer === node ? forInOrOfStatement : undefined; case SyntaxKind.ParenthesizedExpression: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.SpreadElement: @@ -4496,37 +4499,64 @@ export function getAssignmentTargetKind(node: Node): AssignmentKind { break; case SyntaxKind.ShorthandPropertyAssignment: if ((parent as ShorthandPropertyAssignment).name !== node) { - return AssignmentKind.None; + return undefined; } node = parent.parent; break; case SyntaxKind.PropertyAssignment: - if ((parent as ShorthandPropertyAssignment).name === node) { - return AssignmentKind.None; + if ((parent as PropertyAssignment).name === node) { + return undefined; } node = parent.parent; break; default: - return AssignmentKind.None; + return undefined; } parent = node.parent; } } +/** @internal */ +export function getAssignmentTargetKind(node: Node): AssignmentKind { + const target = getAssignmentTarget(node); + if (!target) { + return AssignmentKind.None; + } + switch (target.kind) { + case SyntaxKind.BinaryExpression: + const binaryOperator = target.operatorToken.kind; + return binaryOperator === SyntaxKind.EqualsToken || isLogicalOrCoalescingAssignmentOperator(binaryOperator) ? + AssignmentKind.Definite : + AssignmentKind.Compound; + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: + return AssignmentKind.Compound; + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + return AssignmentKind.Definite; + } +} + // A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property // assignment in an object literal that is an assignment target, or if it is parented by an array literal that is // an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ a }] = xxx'. // (Note that `p` is not a target in the above examples, only `a`.) /** @internal */ export function isAssignmentTarget(node: Node): boolean { - return getAssignmentTargetKind(node) !== AssignmentKind.None; + return !!getAssignmentTarget(node); } -function isCompoundLikeAssignment(assignment: BinaryExpression): boolean { +function isCompoundLikeAssignment(assignment: AssignmentExpression): boolean { const right = skipParentheses(assignment.right); return right.kind === SyntaxKind.BinaryExpression && isAdditiveOperatorOrHigher((right as BinaryExpression).operatorToken.kind); } +/** @internal */ +export function isInCompoundLikeAssignment(node: Node): boolean { + const target = getAssignmentTarget(node); + return !!target && isAssignmentExpression(target, /*excludeCompoundAssignment*/ true) && isCompoundLikeAssignment(target); +} + /** @internal */ export type NodeWithPossibleHoistedDeclaration = | Block @@ -4539,8 +4569,7 @@ export type NodeWithPossibleHoistedDeclaration = | DefaultClause | LabeledStatement | ForStatement - | ForInStatement - | ForOfStatement + | ForInOrOfStatement | DoStatement | WhileStatement | TryStatement From 104b97a7741ae3d2cb11cdd9ef3df158884df9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 27 Jun 2023 00:55:01 +0200 Subject: [PATCH 4/4] support shift operators --- src/compiler/factory/utilities.ts | 6 +- src/compiler/utilities.ts | 4 +- ...ideningWithCompoundLikeAssignments.symbols | 50 +++++++++++++--- ...lWideningWithCompoundLikeAssignments.types | 59 ++++++++++++++++--- ...eralWideningWithCompoundLikeAssignments.ts | 17 +++++- 5 files changed, 110 insertions(+), 26 deletions(-) diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index 273eba30ee3ed..0cd26633e2c81 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -1211,8 +1211,7 @@ function isAdditiveOperator(kind: SyntaxKind): kind is AdditiveOperator { || kind === SyntaxKind.MinusToken; } -/** @internal */ -export function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher { +function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher { return isAdditiveOperator(kind) || isMultiplicativeOperatorOrHigher(kind); } @@ -1223,7 +1222,8 @@ function isShiftOperator(kind: SyntaxKind): kind is ShiftOperator { || kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken; } -function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher { +/** @internal */ +export function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher { return isShiftOperator(kind) || isAdditiveOperatorOrHigher(kind); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2b8c9646bbcb5..2deaf5a8b509d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -232,7 +232,6 @@ import { InterfaceDeclaration, InternalEmitFlags, isAccessor, - isAdditiveOperatorOrHigher, isAnyDirectorySeparator, isArray, isArrayLiteralExpression, @@ -330,6 +329,7 @@ import { isQualifiedName, isRootedDiskPath, isSetAccessorDeclaration, + isShiftOperatorOrHigher, isShorthandPropertyAssignment, isSourceFile, isString, @@ -4548,7 +4548,7 @@ export function isAssignmentTarget(node: Node): boolean { function isCompoundLikeAssignment(assignment: AssignmentExpression): boolean { const right = skipParentheses(assignment.right); - return right.kind === SyntaxKind.BinaryExpression && isAdditiveOperatorOrHigher((right as BinaryExpression).operatorToken.kind); + return right.kind === SyntaxKind.BinaryExpression && isShiftOperatorOrHigher((right as BinaryExpression).operatorToken.kind); } /** @internal */ diff --git a/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.symbols b/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.symbols index 5d594064803d9..599027e35ffc5 100644 --- a/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.symbols +++ b/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.symbols @@ -86,18 +86,50 @@ t6 = t6 % 42 t6 // number >t6 : Symbol(t6, Decl(literalWideningWithCompoundLikeAssignments.ts, 29, 3)) +let t7 = numLiteral; +>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3)) +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +t7 = t7 >> 0 +>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3)) +>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3)) + +t7 // number +>t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 33, 3)) + +let t8 = numLiteral; +>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3)) +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +t8 = t8 >>> 0 +>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3)) +>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3)) + +t8 // number +>t8 : Symbol(t8, Decl(literalWideningWithCompoundLikeAssignments.ts, 37, 3)) + +let t9 = numLiteral; +>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3)) +>numLiteral : Symbol(numLiteral, Decl(literalWideningWithCompoundLikeAssignments.ts, 7, 13)) + +t9 = t9 << 0 +>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3)) +>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3)) + +t9 // number +>t9 : Symbol(t9, Decl(literalWideningWithCompoundLikeAssignments.ts, 41, 3)) declare const literalUnion: "a" | 0; ->literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 34, 13)) +>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 45, 13)) -let t7 = literalUnion; ->t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3)) ->literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 34, 13)) +let t10 = literalUnion; +>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3)) +>literalUnion : Symbol(literalUnion, Decl(literalWideningWithCompoundLikeAssignments.ts, 45, 13)) -t7 = t7 + 'b' ->t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3)) ->t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3)) +t10 = t10 + 'b' +>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3)) +>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3)) -t7 // string ->t7 : Symbol(t7, Decl(literalWideningWithCompoundLikeAssignments.ts, 35, 3)) +t10 // string +>t10 : Symbol(t10, Decl(literalWideningWithCompoundLikeAssignments.ts, 46, 3)) diff --git a/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.types b/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.types index 1b278c910edf3..b605622a24b9c 100644 --- a/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.types +++ b/tests/baselines/reference/literalWideningWithCompoundLikeAssignments.types @@ -108,21 +108,62 @@ t6 = t6 % 42 t6 // number >t6 : number +let t7 = numLiteral; +>t7 : 0 +>numLiteral : 0 + +t7 = t7 >> 0 +>t7 = t7 >> 0 : number +>t7 : number +>t7 >> 0 : number +>t7 : 0 +>0 : 0 + +t7 // number +>t7 : number + +let t8 = numLiteral; +>t8 : 0 +>numLiteral : 0 + +t8 = t8 >>> 0 +>t8 = t8 >>> 0 : number +>t8 : number +>t8 >>> 0 : number +>t8 : 0 +>0 : 0 + +t8 // number +>t8 : number + +let t9 = numLiteral; +>t9 : 0 +>numLiteral : 0 + +t9 = t9 << 0 +>t9 = t9 << 0 : number +>t9 : number +>t9 << 0 : number +>t9 : 0 +>0 : 0 + +t9 // number +>t9 : number declare const literalUnion: "a" | 0; >literalUnion : 0 | "a" -let t7 = literalUnion; ->t7 : 0 | "a" +let t10 = literalUnion; +>t10 : 0 | "a" >literalUnion : 0 | "a" -t7 = t7 + 'b' ->t7 = t7 + 'b' : string ->t7 : string | number ->t7 + 'b' : string ->t7 : 0 | "a" +t10 = t10 + 'b' +>t10 = t10 + 'b' : string +>t10 : string | number +>t10 + 'b' : string +>t10 : 0 | "a" >'b' : "b" -t7 // string ->t7 : string +t10 // string +>t10 : string diff --git a/tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts b/tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts index da4a7fb0af86b..e61b19c228058 100644 --- a/tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts +++ b/tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts @@ -34,8 +34,19 @@ let t6 = numLiteral; t6 = t6 % 42 t6 // number +let t7 = numLiteral; +t7 = t7 >> 0 +t7 // number + +let t8 = numLiteral; +t8 = t8 >>> 0 +t8 // number + +let t9 = numLiteral; +t9 = t9 << 0 +t9 // number declare const literalUnion: "a" | 0; -let t7 = literalUnion; -t7 = t7 + 'b' -t7 // string +let t10 = literalUnion; +t10 = t10 + 'b' +t10 // string