Skip to content

Widen widening literal types through compound-like assignments #52493

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 9 commits into from
Aug 15, 2023
Merged
15 changes: 10 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 &&
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
11 changes: 9 additions & 2 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ import {
InterfaceDeclaration,
InternalEmitFlags,
isAccessor,
isAdditiveOperatorOrHigher,
isAnyDirectorySeparator,
isArray,
isArrayLiteralExpression,
Expand Down Expand Up @@ -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 */
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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))

Original file line number Diff line number Diff line change
@@ -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

41 changes: 41 additions & 0 deletions tests/cases/compiler/literalWideningWithCompoundLikeAssignments.ts
Original file line number Diff line number Diff line change
@@ -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