From 62b489fcaf85de87289a504c8813792a28f6509d Mon Sep 17 00:00:00 2001 From: mehmet turac Date: Mon, 31 Aug 2026 00:57:39 +0300 Subject: [PATCH] fix(emit): emit 1/0 and 0/0 instead of Infinity/NaN in enum transforms Enum values that evaluate to Infinity, -Infinity, or NaN were emitted as bare identifier references. When a local variable shadows the global Infinity or NaN, the emitted code captures the wrong value. Emit the numeric expressions 1/0, -(1/0), and 0/0 instead, which evaluate to IEEE 754 positive infinity, negative infinity, and NaN regardless of any identifier shadowing. Fixes #55091 --- .../transformers/inliners/constenum.go | 7 +- .../transformers/tstransforms/utilities.go | 7 +- .../compiler/enumAutoIncrementValue.js | 4 +- .../compiler/enumInfinityShadowed.errors.txt | 36 +++++++ .../compiler/enumInfinityShadowed.js | 46 +++++++++ .../compiler/enumInfinityShadowed.symbols | 70 ++++++++++++++ .../compiler/enumInfinityShadowed.types | 94 +++++++++++++++++++ .../reference/compiler/fakeInfinity2.js | 4 +- .../reference/compiler/fakeInfinity3.js | 4 +- .../cases/compiler/enumInfinityShadowed.ts | 26 +++++ 10 files changed, 286 insertions(+), 12 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.js create mode 100644 tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.types create mode 100644 tsc/testdata/tests/cases/compiler/enumInfinityShadowed.ts diff --git a/tsc/internal/transformers/inliners/constenum.go b/tsc/internal/transformers/inliners/constenum.go index f3be363c8393a..4d0e3e7715b77 100644 --- a/tsc/internal/transformers/inliners/constenum.go +++ b/tsc/internal/transformers/inliners/constenum.go @@ -43,13 +43,14 @@ func (tx *ConstEnumInliningTransformer) visit(node *ast.Node) *ast.Node { switch v := value.(type) { case jsnum.Number: if v.IsInf() { + inf := tx.Factory().NewBinaryExpression(nil, tx.Factory().NewNumericLiteral("1", ast.TokenFlagsNone), nil, tx.Factory().NewToken(ast.KindSlashToken), tx.Factory().NewNumericLiteral("0", ast.TokenFlagsNone)) if v.Abs() == v { - replacement = tx.Factory().NewIdentifier("Infinity") + replacement = inf } else { - replacement = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewIdentifier("Infinity")) + replacement = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, inf) } } else if v.IsNaN() { - replacement = tx.Factory().NewIdentifier("NaN") + replacement = tx.Factory().NewBinaryExpression(nil, tx.Factory().NewNumericLiteral("0", ast.TokenFlagsNone), nil, tx.Factory().NewToken(ast.KindSlashToken), tx.Factory().NewNumericLiteral("0", ast.TokenFlagsNone)) } else if v.Abs() == v { replacement = tx.Factory().NewNumericLiteral(v.String(), ast.TokenFlagsNone) } else { diff --git a/tsc/internal/transformers/tstransforms/utilities.go b/tsc/internal/transformers/tstransforms/utilities.go index 2c0f0eb5478c6..ba6eb45360597 100644 --- a/tsc/internal/transformers/tstransforms/utilities.go +++ b/tsc/internal/transformers/tstransforms/utilities.go @@ -12,13 +12,14 @@ func constantExpression(value any, factory *printer.NodeFactory) *ast.Expression return factory.NewStringLiteral(value, ast.TokenFlagsNone) case jsnum.Number: if value.IsInf() { + inf := factory.NewBinaryExpression(nil, factory.NewNumericLiteral("1", ast.TokenFlagsNone), nil, factory.NewToken(ast.KindSlashToken), factory.NewNumericLiteral("0", ast.TokenFlagsNone)) if value > 0 { - return factory.NewIdentifier("Infinity") + return inf } - return factory.NewPrefixUnaryExpression(ast.KindMinusToken, factory.NewIdentifier("Infinity")) + return factory.NewPrefixUnaryExpression(ast.KindMinusToken, inf) } if value.IsNaN() { - return factory.NewIdentifier("NaN") + return factory.NewBinaryExpression(nil, factory.NewNumericLiteral("0", ast.TokenFlagsNone), nil, factory.NewToken(ast.KindSlashToken), factory.NewNumericLiteral("0", ast.TokenFlagsNone)) } if value < 0 { return factory.NewPrefixUnaryExpression(ast.KindMinusToken, constantExpression(-value, factory)) diff --git a/tsc/testdata/baselines/reference/compiler/enumAutoIncrementValue.js b/tsc/testdata/baselines/reference/compiler/enumAutoIncrementValue.js index d42086ad7a05d..e2f405d5fc526 100644 --- a/tsc/testdata/baselines/reference/compiler/enumAutoIncrementValue.js +++ b/tsc/testdata/baselines/reference/compiler/enumAutoIncrementValue.js @@ -11,6 +11,6 @@ enum E { "use strict"; var E; (function (E) { - E[E["A"] = NaN] = "A"; - E[E["B"] = NaN] = "B"; + E[E["A"] = 0 / 0] = "A"; + E[E["B"] = 0 / 0] = "B"; })(E || (E = {})); diff --git a/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.errors.txt b/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.errors.txt new file mode 100644 index 0000000000000..03e9769b99148 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.errors.txt @@ -0,0 +1,36 @@ +enumInfinityShadowed.ts(13,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +enumInfinityShadowed.ts(14,9): error TS2477: 'const' enum member initializer was evaluated to a non-finite value. +enumInfinityShadowed.ts(15,9): error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. + + +==== enumInfinityShadowed.ts (3 errors) ==== + // Fixes https://github.com/microsoft/TypeScript/issues/55091 + // Enum values that evaluate to Infinity, -Infinity, or NaN must not emit + // bare Infinity/NaN identifiers, because those can be shadowed by local + // variables. Instead, emit 1/0, -(1/0), and 0/0 respectively. + + enum A { + X = 1 / 0, + Y = -1 / 0, + Z = 0 / 0, + } + + const enum B { + X = 1 / 0, + ~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + Y = -1 / 0, + ~~~~~~ +!!! error TS2477: 'const' enum member initializer was evaluated to a non-finite value. + Z = 0 / 0, + ~~~~~ +!!! error TS2478: 'const' enum member initializer was evaluated to disallowed value 'NaN'. + } + + { + let Infinity = 3; + let NaN = 42; + console.log(A.X, A.Y, A.Z); + console.log(B.X, B.Y, B.Z); + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.js b/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.js new file mode 100644 index 0000000000000..417816432534b --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.js @@ -0,0 +1,46 @@ +//// [tests/cases/compiler/enumInfinityShadowed.ts] //// + +//// [enumInfinityShadowed.ts] +// Fixes https://github.com/microsoft/TypeScript/issues/55091 +// Enum values that evaluate to Infinity, -Infinity, or NaN must not emit +// bare Infinity/NaN identifiers, because those can be shadowed by local +// variables. Instead, emit 1/0, -(1/0), and 0/0 respectively. + +enum A { + X = 1 / 0, + Y = -1 / 0, + Z = 0 / 0, +} + +const enum B { + X = 1 / 0, + Y = -1 / 0, + Z = 0 / 0, +} + +{ + let Infinity = 3; + let NaN = 42; + console.log(A.X, A.Y, A.Z); + console.log(B.X, B.Y, B.Z); +} + + +//// [enumInfinityShadowed.js] +"use strict"; +// Fixes https://github.com/microsoft/TypeScript/issues/55091 +// Enum values that evaluate to Infinity, -Infinity, or NaN must not emit +// bare Infinity/NaN identifiers, because those can be shadowed by local +// variables. Instead, emit 1/0, -(1/0), and 0/0 respectively. +var A; +(function (A) { + A[A["X"] = 1 / 0] = "X"; + A[A["Y"] = -(1 / 0)] = "Y"; + A[A["Z"] = 0 / 0] = "Z"; +})(A || (A = {})); +{ + let Infinity = 3; + let NaN = 42; + console.log(A.X, A.Y, A.Z); + console.log(1 / 0 /* B.X */, -(1 / 0) /* B.Y */, 0 / 0 /* B.Z */); +} diff --git a/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.symbols b/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.symbols new file mode 100644 index 0000000000000..21dfb3583bb27 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.symbols @@ -0,0 +1,70 @@ +//// [tests/cases/compiler/enumInfinityShadowed.ts] //// + +=== enumInfinityShadowed.ts === +// Fixes https://github.com/microsoft/TypeScript/issues/55091 +// Enum values that evaluate to Infinity, -Infinity, or NaN must not emit +// bare Infinity/NaN identifiers, because those can be shadowed by local +// variables. Instead, emit 1/0, -(1/0), and 0/0 respectively. + +enum A { +>A : Symbol(A, Decl(enumInfinityShadowed.ts, 0, 0)) + + X = 1 / 0, +>X : Symbol(A.X, Decl(enumInfinityShadowed.ts, 5, 8)) + + Y = -1 / 0, +>Y : Symbol(A.Y, Decl(enumInfinityShadowed.ts, 6, 14)) + + Z = 0 / 0, +>Z : Symbol(A.Z, Decl(enumInfinityShadowed.ts, 7, 15)) +} + +const enum B { +>B : Symbol(B, Decl(enumInfinityShadowed.ts, 9, 1)) + + X = 1 / 0, +>X : Symbol(B.X, Decl(enumInfinityShadowed.ts, 11, 14)) + + Y = -1 / 0, +>Y : Symbol(B.Y, Decl(enumInfinityShadowed.ts, 12, 14)) + + Z = 0 / 0, +>Z : Symbol(B.Z, Decl(enumInfinityShadowed.ts, 13, 15)) +} + +{ + let Infinity = 3; +>Infinity : Symbol(Infinity, Decl(enumInfinityShadowed.ts, 18, 7)) + + let NaN = 42; +>NaN : Symbol(NaN, Decl(enumInfinityShadowed.ts, 19, 7)) + + console.log(A.X, A.Y, A.Z); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>A.X : Symbol(A.X, Decl(enumInfinityShadowed.ts, 5, 8)) +>A : Symbol(A, Decl(enumInfinityShadowed.ts, 0, 0)) +>X : Symbol(A.X, Decl(enumInfinityShadowed.ts, 5, 8)) +>A.Y : Symbol(A.Y, Decl(enumInfinityShadowed.ts, 6, 14)) +>A : Symbol(A, Decl(enumInfinityShadowed.ts, 0, 0)) +>Y : Symbol(A.Y, Decl(enumInfinityShadowed.ts, 6, 14)) +>A.Z : Symbol(A.Z, Decl(enumInfinityShadowed.ts, 7, 15)) +>A : Symbol(A, Decl(enumInfinityShadowed.ts, 0, 0)) +>Z : Symbol(A.Z, Decl(enumInfinityShadowed.ts, 7, 15)) + + console.log(B.X, B.Y, B.Z); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>B.X : Symbol(B.X, Decl(enumInfinityShadowed.ts, 11, 14)) +>B : Symbol(B, Decl(enumInfinityShadowed.ts, 9, 1)) +>X : Symbol(B.X, Decl(enumInfinityShadowed.ts, 11, 14)) +>B.Y : Symbol(B.Y, Decl(enumInfinityShadowed.ts, 12, 14)) +>B : Symbol(B, Decl(enumInfinityShadowed.ts, 9, 1)) +>Y : Symbol(B.Y, Decl(enumInfinityShadowed.ts, 12, 14)) +>B.Z : Symbol(B.Z, Decl(enumInfinityShadowed.ts, 13, 15)) +>B : Symbol(B, Decl(enumInfinityShadowed.ts, 9, 1)) +>Z : Symbol(B.Z, Decl(enumInfinityShadowed.ts, 13, 15)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.types b/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.types new file mode 100644 index 0000000000000..3cbbef5400da0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/enumInfinityShadowed.types @@ -0,0 +1,94 @@ +//// [tests/cases/compiler/enumInfinityShadowed.ts] //// + +=== enumInfinityShadowed.ts === +// Fixes https://github.com/microsoft/TypeScript/issues/55091 +// Enum values that evaluate to Infinity, -Infinity, or NaN must not emit +// bare Infinity/NaN identifiers, because those can be shadowed by local +// variables. Instead, emit 1/0, -(1/0), and 0/0 respectively. + +enum A { +>A : A + + X = 1 / 0, +>X : A.X +>1 / 0 : number +>1 : 1 +>0 : 0 + + Y = -1 / 0, +>Y : A.Y +>-1 / 0 : number +>-1 : -1 +>1 : 1 +>0 : 0 + + Z = 0 / 0, +>Z : A.Z +>0 / 0 : number +>0 : 0 +>0 : 0 +} + +const enum B { +>B : B + + X = 1 / 0, +>X : B.X +>1 / 0 : number +>1 : 1 +>0 : 0 + + Y = -1 / 0, +>Y : B.Y +>-1 / 0 : number +>-1 : -1 +>1 : 1 +>0 : 0 + + Z = 0 / 0, +>Z : B.Z +>0 / 0 : number +>0 : 0 +>0 : 0 +} + +{ + let Infinity = 3; +>Infinity : number +>3 : 3 + + let NaN = 42; +>NaN : number +>42 : 42 + + console.log(A.X, A.Y, A.Z); +>console.log(A.X, A.Y, A.Z) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>A.X : A.X +>A : typeof A +>X : A.X +>A.Y : A.Y +>A : typeof A +>Y : A.Y +>A.Z : A.Z +>A : typeof A +>Z : A.Z + + console.log(B.X, B.Y, B.Z); +>console.log(B.X, B.Y, B.Z) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>B.X : B.X +>B : typeof B +>X : B.X +>B.Y : B.Y +>B : typeof B +>Y : B.Y +>B.Z : B.Z +>B : typeof B +>Z : B.Z +} + diff --git a/tsc/testdata/baselines/reference/compiler/fakeInfinity2.js b/tsc/testdata/baselines/reference/compiler/fakeInfinity2.js index 6c2bf80435ef2..0a916608ca8ef 100644 --- a/tsc/testdata/baselines/reference/compiler/fakeInfinity2.js +++ b/tsc/testdata/baselines/reference/compiler/fakeInfinity2.js @@ -21,8 +21,8 @@ export const m = X.f(); //// [fakeInfinity2.js] export var Foo; (function (Foo) { - Foo[Foo["A"] = Infinity] = "A"; - Foo[Foo["B"] = -Infinity] = "B"; + Foo[Foo["A"] = 1 / 0] = "A"; + Foo[Foo["B"] = -(1 / 0)] = "B"; })(Foo || (Foo = {})); var X; (function (X) { diff --git a/tsc/testdata/baselines/reference/compiler/fakeInfinity3.js b/tsc/testdata/baselines/reference/compiler/fakeInfinity3.js index dbde9c2ce72b8..947a56054e552 100644 --- a/tsc/testdata/baselines/reference/compiler/fakeInfinity3.js +++ b/tsc/testdata/baselines/reference/compiler/fakeInfinity3.js @@ -26,8 +26,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.Infinity = exports.m = exports.Foo = void 0; var Foo; (function (Foo) { - Foo[Foo["A"] = Infinity] = "A"; - Foo[Foo["B"] = -Infinity] = "B"; + Foo[Foo["A"] = 1 / 0] = "A"; + Foo[Foo["B"] = -(1 / 0)] = "B"; })(Foo || (exports.Foo = Foo = {})); var X; (function (X) { diff --git a/tsc/testdata/tests/cases/compiler/enumInfinityShadowed.ts b/tsc/testdata/tests/cases/compiler/enumInfinityShadowed.ts new file mode 100644 index 0000000000000..160cae5e0bdf8 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/enumInfinityShadowed.ts @@ -0,0 +1,26 @@ +// @target: es2015 +// @strict: true + +// Fixes https://github.com/microsoft/TypeScript/issues/55091 +// Enum values that evaluate to Infinity, -Infinity, or NaN must not emit +// bare Infinity/NaN identifiers, because those can be shadowed by local +// variables. Instead, emit 1/0, -(1/0), and 0/0 respectively. + +enum A { + X = 1 / 0, + Y = -1 / 0, + Z = 0 / 0, +} + +const enum B { + X = 1 / 0, + Y = -1 / 0, + Z = 0 / 0, +} + +{ + let Infinity = 3; + let NaN = 42; + console.log(A.X, A.Y, A.Z); + console.log(B.X, B.Y, B.Z); +}