Skip to content

Commit 4328695

Browse files
committed
Emit valid syntax for infinite numeric literals
Preserve original numeric text when reusable and use 1e999 as a valid infinity spelling for synthesized types, declarations, enum output, completions, and hover.
1 parent ffd0bd2 commit 4328695

27 files changed

Lines changed: 206 additions & 218 deletions

‎tsc/internal/checker/emitresolver.go‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -705,9 +705,12 @@ func (r *EmitResolver) CreateLiteralConstValue(node *ast.Node, tracker nodebuild
705705
case jsnum.Number:
706706
if value.IsInf() {
707707
if value > 0 {
708-
return r.emitContext.Factory.NewIdentifier("Infinity")
708+
return r.emitContext.Factory.NewNumericLiteral(jsnum.InfinityLiteralText, ast.TokenFlagsNone)
709709
}
710-
return r.emitContext.Factory.NewPrefixUnaryExpression(ast.KindMinusToken, r.emitContext.Factory.NewIdentifier("Infinity"))
710+
return r.emitContext.Factory.NewPrefixUnaryExpression(
711+
ast.KindMinusToken,
712+
r.emitContext.Factory.NewNumericLiteral(jsnum.InfinityLiteralText, ast.TokenFlagsNone),
713+
)
711714
}
712715
if value.IsNaN() {
713716
return r.emitContext.Factory.NewIdentifier("NaN")

‎tsc/internal/checker/nodebuilder_hover.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ func (b *NodeBuilderImpl) enumMemberInitializer(p *ast.Symbol) *ast.Node {
102102
case string:
103103
return b.f.NewStringLiteral(v, 0)
104104
case jsnum.Number:
105+
if v.IsInf() {
106+
if v < 0 {
107+
return b.f.NewPrefixUnaryExpression(ast.KindMinusToken, b.f.NewNumericLiteral(jsnum.InfinityLiteralText, 0))
108+
}
109+
return b.f.NewNumericLiteral(jsnum.InfinityLiteralText, 0)
110+
}
105111
return b.f.NewNumericLiteral(v.String(), 0)
106112
}
107113
return nil

‎tsc/internal/checker/nodebuilderimpl.go‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3447,11 +3447,17 @@ func (b *NodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
34473447
}
34483448
if t.flags&TypeFlagsNumberLiteral != 0 {
34493449
value := t.AsLiteralType().value.(jsnum.Number)
3450-
b.ctx.approximateLength += len(value.String())
3451-
if value < 0 {
3452-
return b.f.NewLiteralTypeNode(b.f.NewPrefixUnaryExpression(ast.KindMinusToken, b.f.NewNumericLiteral(value.String()[1:], ast.TokenFlagsNone)))
3450+
negative := value < 0
3451+
text := value.Abs().String()
3452+
if value.IsInf() {
3453+
text = jsnum.InfinityLiteralText
3454+
}
3455+
b.ctx.approximateLength += len(text)
3456+
if negative {
3457+
b.ctx.approximateLength++
3458+
return b.f.NewLiteralTypeNode(b.f.NewPrefixUnaryExpression(ast.KindMinusToken, b.f.NewNumericLiteral(text, ast.TokenFlagsNone)))
34533459
} else {
3454-
return b.f.NewLiteralTypeNode(b.f.NewNumericLiteral(value.String(), ast.TokenFlagsNone))
3460+
return b.f.NewLiteralTypeNode(b.f.NewNumericLiteral(text, ast.TokenFlagsNone))
34553461
}
34563462
}
34573463
if t.flags&TypeFlagsBigIntLiteral != 0 {

‎tsc/internal/checker/nodecopy.go‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55

66
"github.com/microsoft/TypeScript/tsc/internal/ast"
77
"github.com/microsoft/TypeScript/tsc/internal/core"
8+
"github.com/microsoft/TypeScript/tsc/internal/jsnum"
89
"github.com/microsoft/TypeScript/tsc/internal/nodebuilder"
910
"github.com/microsoft/TypeScript/tsc/internal/printer"
11+
"github.com/microsoft/TypeScript/tsc/internal/scanner"
1012
)
1113

1214
func (b *NodeBuilderImpl) reuseNode(node *ast.Node) *ast.Node {
@@ -26,6 +28,14 @@ func (b *NodeBuilderImpl) reuseName(node *ast.Node, isMethod bool) *ast.Node {
2628
if res == nil {
2729
return res
2830
}
31+
if ast.IsComputedPropertyName(node) && ast.IsComputedPropertyName(res) {
32+
sourceLiteral := numericLiteralOperand(node.AsComputedPropertyName().Expression)
33+
reusedLiteral := numericLiteralOperand(res.AsComputedPropertyName().Expression)
34+
if sourceLiteral != nil && reusedLiteral != nil && jsnum.FromString(sourceLiteral.Text()).IsInf() {
35+
sourceFile := ast.GetSourceFileOfNode(sourceLiteral)
36+
reusedLiteral.LiteralLikeData().Text = scanner.GetSourceTextOfNodeFromSourceFile(sourceFile, sourceLiteral, false)
37+
}
38+
}
2939

3040
text, ok := ast.TryGetTextOfPropertyName(res)
3141
if !ok {
@@ -53,6 +63,17 @@ func (b *NodeBuilderImpl) reuseName(node *ast.Node, isMethod bool) *ast.Node {
5363
return b.setTextRange(renamed, res)
5464
}
5565

66+
func numericLiteralOperand(node *ast.Node) *ast.Node {
67+
node = ast.SkipParentheses(node)
68+
if ast.IsPrefixUnaryExpression(node) {
69+
node = ast.SkipParentheses(node.AsPrefixUnaryExpression().Operand)
70+
}
71+
if ast.IsNumericLiteral(node) {
72+
return node
73+
}
74+
return nil
75+
}
76+
5677
func (b *NodeBuilderImpl) reuseTypeNode(node *ast.Node) *ast.Node {
5778
if node == nil {
5879
return node

‎tsc/internal/checker/pseudotypenodebuilder.go‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package checker
33
import (
44
"github.com/microsoft/TypeScript/tsc/internal/ast"
55
"github.com/microsoft/TypeScript/tsc/internal/debug"
6+
"github.com/microsoft/TypeScript/tsc/internal/jsnum"
67
"github.com/microsoft/TypeScript/tsc/internal/nodebuilder"
78
"github.com/microsoft/TypeScript/tsc/internal/printer"
89
"github.com/microsoft/TypeScript/tsc/internal/pseudochecker"
10+
"github.com/microsoft/TypeScript/tsc/internal/scanner"
911
)
1012

1113
// pseudoTypeToNodeWithCheckerFallback is like pseudoTypeToNode but when the top-level pseudo type
@@ -317,13 +319,41 @@ func (b *NodeBuilderImpl) pseudoTypeToNode(t *pseudochecker.PseudoType) *ast.Nod
317319
return result
318320
case pseudochecker.PseudoTypeKindStringLiteral, pseudochecker.PseudoTypeKindNumericLiteral, pseudochecker.PseudoTypeKindBigIntLiteral:
319321
source := t.AsPseudoTypeLiteral().Node
320-
return b.f.NewLiteralTypeNode(b.reuseNode(source))
322+
reused := b.reuseNode(source)
323+
if t.Kind == pseudochecker.PseudoTypeKindNumericLiteral {
324+
sourceLiteral := source
325+
reusedLiteral := reused
326+
if ast.IsPrefixUnaryExpression(source) {
327+
sourceLiteral = source.AsPrefixUnaryExpression().Operand
328+
reusedLiteral = reused.AsPrefixUnaryExpression().Operand
329+
}
330+
if isDirectlyConstAssertedNumericLiteral(source) {
331+
// Numeric literal text stores the rounded value, so recover the source spelling for reused const assertions.
332+
sourceFile := ast.GetSourceFileOfNode(sourceLiteral)
333+
reusedLiteral.LiteralLikeData().Text = scanner.GetSourceTextOfNodeFromSourceFile(sourceFile, sourceLiteral, false)
334+
} else if jsnum.FromString(sourceLiteral.Text()).IsInf() {
335+
reusedLiteral.LiteralLikeData().Text = jsnum.InfinityLiteralText
336+
}
337+
}
338+
return b.f.NewLiteralTypeNode(reused)
321339
default:
322340
debug.AssertNever(t.Kind, "Unhandled pseudotype kind in pseudotype node construction")
323341
return nil
324342
}
325343
}
326344

345+
func isDirectlyConstAssertedNumericLiteral(node *ast.Node) bool {
346+
for parent := node.Parent; parent != nil; parent = parent.Parent {
347+
if ast.IsConstAssertion(parent) {
348+
return true
349+
}
350+
if !ast.IsPrefixUnaryExpression(parent) && !ast.IsParenthesizedExpression(parent) {
351+
return false
352+
}
353+
}
354+
return false
355+
}
356+
327357
func (b *NodeBuilderImpl) pseudoParametersToNodeList(params []*pseudochecker.PseudoParameter) *ast.NodeList {
328358
res := make([]*ast.Node, 0, len(params))
329359
for _, p := range params {

‎tsc/internal/jsnum/string.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"github.com/microsoft/TypeScript/tsc/internal/stringutil"
1414
)
1515

16+
const InfinityLiteralText = "1e999"
17+
1618
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-tostring
1719
func (n Number) String() string {
1820
switch {

‎tsc/internal/ls/completions.go‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6660,10 +6660,14 @@ func (l *LanguageService) getExhaustiveCaseSnippets(
66606660
elements = append(elements, bigInt)
66616661
case jsnum.Number:
66626662
var number *ast.Node
6663+
text := v.Abs().String()
6664+
if v.IsInf() {
6665+
text = jsnum.InfinityLiteralText
6666+
}
66636667
if v < 0 {
6664-
number = factory.NewPrefixUnaryExpression(ast.KindMinusToken, factory.NewNumericLiteral(v.Abs().String(), ast.TokenFlagsNone))
6668+
number = factory.NewPrefixUnaryExpression(ast.KindMinusToken, factory.NewNumericLiteral(text, ast.TokenFlagsNone))
66656669
} else {
6666-
number = factory.NewNumericLiteral(v.String(), ast.TokenFlagsNone)
6670+
number = factory.NewNumericLiteral(text, ast.TokenFlagsNone)
66676671
}
66686672
elements = append(elements, number)
66696673
case string:

‎tsc/internal/transformers/declarations/transform.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,9 +2291,9 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar
22912291
case jsnum.Number:
22922292
if value.IsInf() {
22932293
if value > 0 {
2294-
newInitializer = tx.Factory().NewIdentifier("Infinity")
2294+
newInitializer = tx.Factory().NewNumericLiteral(jsnum.InfinityLiteralText, ast.TokenFlagsNone)
22952295
} else {
2296-
newInitializer = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewIdentifier("Infinity"))
2296+
newInitializer = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewNumericLiteral(jsnum.InfinityLiteralText, ast.TokenFlagsNone))
22972297
}
22982298
} else if value.IsNaN() {
22992299
newInitializer = tx.Factory().NewIdentifier("NaN")

‎tsc/internal/transformers/inliners/constenum.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ func (tx *ConstEnumInliningTransformer) visit(node *ast.Node) *ast.Node {
4444
case jsnum.Number:
4545
if v.IsInf() {
4646
if v.Abs() == v {
47-
replacement = tx.Factory().NewIdentifier("Infinity")
47+
replacement = tx.Factory().NewNumericLiteral(jsnum.InfinityLiteralText, ast.TokenFlagsNone)
4848
} else {
49-
replacement = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewIdentifier("Infinity"))
49+
replacement = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewNumericLiteral(jsnum.InfinityLiteralText, ast.TokenFlagsNone))
5050
}
5151
} else if v.IsNaN() {
5252
replacement = tx.Factory().NewIdentifier("NaN")

‎tsc/internal/transformers/tstransforms/utilities.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ func constantExpression(value any, factory *printer.NodeFactory) *ast.Expression
1313
case jsnum.Number:
1414
if value.IsInf() {
1515
if value > 0 {
16-
return factory.NewIdentifier("Infinity")
16+
return factory.NewNumericLiteral(jsnum.InfinityLiteralText, ast.TokenFlagsNone)
1717
}
18-
return factory.NewPrefixUnaryExpression(ast.KindMinusToken, factory.NewIdentifier("Infinity"))
18+
return factory.NewPrefixUnaryExpression(ast.KindMinusToken, factory.NewNumericLiteral(jsnum.InfinityLiteralText, ast.TokenFlagsNone))
1919
}
2020
if value.IsNaN() {
2121
return factory.NewIdentifier("NaN")

0 commit comments

Comments
 (0)