diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 020fe611931..5e23758e720 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -614,8 +614,10 @@ type Checker struct { evaluate evaluator.Evaluator stringLiteralTypes map[string]*Type numberLiteralTypes map[jsnum.Number]*Type + nanType *Type bigintLiteralTypes map[jsnum.PseudoBigInt]*Type enumLiteralTypes map[EnumLiteralKey]*Type + enumNaNLiteralTypes map[*ast.Symbol]*Type indexedAccessTypes map[CacheHashKey]*Type templateLiteralTypes map[CacheHashKey]*Type stringMappingTypes map[StringMappingKey]*Type @@ -924,6 +926,7 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.numberLiteralTypes = make(map[jsnum.Number]*Type) c.bigintLiteralTypes = make(map[jsnum.PseudoBigInt]*Type) c.enumLiteralTypes = make(map[EnumLiteralKey]*Type) + c.enumNaNLiteralTypes = make(map[*ast.Symbol]*Type) c.indexedAccessTypes = make(map[CacheHashKey]*Type) c.templateLiteralTypes = make(map[CacheHashKey]*Type) c.stringMappingTypes = make(map[StringMappingKey]*Type) @@ -25152,6 +25155,14 @@ func (c *Checker) getStringLiteralType(value string) *Type { } func (c *Checker) getNumberLiteralType(value jsnum.Number) *Type { + // NaN cannot be used as a Go map key because NaN != NaN in IEEE 754, + // so Go map lookups for NaN always miss. Cache NaN type separately. + if value.IsNaN() { + if c.nanType == nil { + c.nanType = c.newLiteralType(TypeFlagsNumberLiteral, value, nil) + } + return c.nanType + } t := c.numberLiteralTypes[value] if t == nil { t = c.newLiteralType(TypeFlagsNumberLiteral, value, nil) @@ -25193,11 +25204,22 @@ func getBooleanLiteralValue(t *Type) bool { func (c *Checker) getEnumLiteralType(value any, enumSymbol *ast.Symbol, symbol *ast.Symbol) *Type { var flags TypeFlags - switch value.(type) { + switch v := value.(type) { case string: flags = TypeFlagsEnumLiteral | TypeFlagsStringLiteral case jsnum.Number: flags = TypeFlagsEnumLiteral | TypeFlagsNumberLiteral + // NaN cannot be used as a Go map key because NaN != NaN in IEEE 754, + // so Go map lookups for NaN always miss. Cache NaN enum types separately by enum symbol. + if v.IsNaN() { + t := c.enumNaNLiteralTypes[enumSymbol] + if t == nil { + t = c.newLiteralType(flags, value, nil) + t.symbol = symbol + c.enumNaNLiteralTypes[enumSymbol] = t + } + return t + } default: panic("Unhandled case in getEnumLiteralType") } diff --git a/testdata/baselines/reference/compiler/enumAutoIncrementValue.types b/testdata/baselines/reference/compiler/enumAutoIncrementValue.types index 900cfc8b844..dd23ea4453a 100644 --- a/testdata/baselines/reference/compiler/enumAutoIncrementValue.types +++ b/testdata/baselines/reference/compiler/enumAutoIncrementValue.types @@ -11,6 +11,6 @@ enum E { >0 : 0 B, ->B : E.B +>B : E.A } diff --git a/testdata/baselines/reference/compiler/enumNaNValues.errors.txt b/testdata/baselines/reference/compiler/enumNaNValues.errors.txt new file mode 100644 index 00000000000..10d8b638b29 --- /dev/null +++ b/testdata/baselines/reference/compiler/enumNaNValues.errors.txt @@ -0,0 +1,20 @@ +enumNaNValues.ts(13,7): error TS2322: Type 'F' is not assignable to type 'E'. + + +==== enumNaNValues.ts (1 errors) ==== + enum E { + A = NaN, + B = NaN, + } + + const a: E.A = E.B; + const b: E.B = E.A; + + enum F { + X = NaN, + } + + const c: E.A = F.X; // Error expected - different enums + ~ +!!! error TS2322: Type 'F' is not assignable to type 'E'. + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/enumNaNValues.js b/testdata/baselines/reference/compiler/enumNaNValues.js new file mode 100644 index 00000000000..88ba034c4d5 --- /dev/null +++ b/testdata/baselines/reference/compiler/enumNaNValues.js @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/enumNaNValues.ts] //// + +//// [enumNaNValues.ts] +enum E { + A = NaN, + B = NaN, +} + +const a: E.A = E.B; +const b: E.B = E.A; + +enum F { + X = NaN, +} + +const c: E.A = F.X; // Error expected - different enums + + +//// [enumNaNValues.js] +"use strict"; +var E; +(function (E) { + E[E["A"] = NaN] = "A"; + E[E["B"] = NaN] = "B"; +})(E || (E = {})); +const a = E.B; +const b = E.A; +var F; +(function (F) { + F[F["X"] = NaN] = "X"; +})(F || (F = {})); +const c = F.X; // Error expected - different enums diff --git a/testdata/baselines/reference/compiler/enumNaNValues.symbols b/testdata/baselines/reference/compiler/enumNaNValues.symbols new file mode 100644 index 00000000000..0d3125bbb81 --- /dev/null +++ b/testdata/baselines/reference/compiler/enumNaNValues.symbols @@ -0,0 +1,47 @@ +//// [tests/cases/compiler/enumNaNValues.ts] //// + +=== enumNaNValues.ts === +enum E { +>E : Symbol(E, Decl(enumNaNValues.ts, 0, 0)) + + A = NaN, +>A : Symbol(E.A, Decl(enumNaNValues.ts, 0, 8)) +>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --)) + + B = NaN, +>B : Symbol(E.B, Decl(enumNaNValues.ts, 1, 10)) +>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --)) +} + +const a: E.A = E.B; +>a : Symbol(a, Decl(enumNaNValues.ts, 5, 5)) +>E : Symbol(E, Decl(enumNaNValues.ts, 0, 0)) +>A : Symbol(E.A, Decl(enumNaNValues.ts, 0, 8)) +>E.B : Symbol(E.B, Decl(enumNaNValues.ts, 1, 10)) +>E : Symbol(E, Decl(enumNaNValues.ts, 0, 0)) +>B : Symbol(E.B, Decl(enumNaNValues.ts, 1, 10)) + +const b: E.B = E.A; +>b : Symbol(b, Decl(enumNaNValues.ts, 6, 5)) +>E : Symbol(E, Decl(enumNaNValues.ts, 0, 0)) +>B : Symbol(E.B, Decl(enumNaNValues.ts, 1, 10)) +>E.A : Symbol(E.A, Decl(enumNaNValues.ts, 0, 8)) +>E : Symbol(E, Decl(enumNaNValues.ts, 0, 0)) +>A : Symbol(E.A, Decl(enumNaNValues.ts, 0, 8)) + +enum F { +>F : Symbol(F, Decl(enumNaNValues.ts, 6, 19)) + + X = NaN, +>X : Symbol(F.X, Decl(enumNaNValues.ts, 8, 8)) +>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --)) +} + +const c: E.A = F.X; // Error expected - different enums +>c : Symbol(c, Decl(enumNaNValues.ts, 12, 5)) +>E : Symbol(E, Decl(enumNaNValues.ts, 0, 0)) +>A : Symbol(E.A, Decl(enumNaNValues.ts, 0, 8)) +>F.X : Symbol(F.X, Decl(enumNaNValues.ts, 8, 8)) +>F : Symbol(F, Decl(enumNaNValues.ts, 6, 19)) +>X : Symbol(F.X, Decl(enumNaNValues.ts, 8, 8)) + diff --git a/testdata/baselines/reference/compiler/enumNaNValues.types b/testdata/baselines/reference/compiler/enumNaNValues.types new file mode 100644 index 00000000000..5417c190e03 --- /dev/null +++ b/testdata/baselines/reference/compiler/enumNaNValues.types @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/enumNaNValues.ts] //// + +=== enumNaNValues.ts === +enum E { +>E : E + + A = NaN, +>A : E.A +>NaN : number + + B = NaN, +>B : E.A +>NaN : number +} + +const a: E.A = E.B; +>a : E +>E : any +>E.B : E +>E : typeof E +>B : E + +const b: E.B = E.A; +>b : E +>E : any +>E.A : E +>E : typeof E +>A : E + +enum F { +>F : F + + X = NaN, +>X : F.X +>NaN : number +} + +const c: E.A = F.X; // Error expected - different enums +>c : E +>E : any +>F.X : F +>F : typeof F +>X : F + diff --git a/testdata/baselines/reference/submodule/conformance/enumConstantMembers.types b/testdata/baselines/reference/submodule/conformance/enumConstantMembers.types index da805aefa1f..5bddfd98720 100644 --- a/testdata/baselines/reference/submodule/conformance/enumConstantMembers.types +++ b/testdata/baselines/reference/submodule/conformance/enumConstantMembers.types @@ -79,7 +79,7 @@ enum E5 { >0.0 : 0 e = NaN, ->e : E5.e +>e : E5.d >NaN : number f = Infinity, @@ -120,7 +120,7 @@ const enum E6 { >0.0 : 0 e = NaN, ->e : E6.e +>e : E6.d >NaN : number f = Infinity, diff --git a/testdata/baselines/reference/submodule/conformance/enumConstantMembers.types.diff b/testdata/baselines/reference/submodule/conformance/enumConstantMembers.types.diff deleted file mode 100644 index d7977f84248..00000000000 --- a/testdata/baselines/reference/submodule/conformance/enumConstantMembers.types.diff +++ /dev/null @@ -1,20 +0,0 @@ ---- old.enumConstantMembers.types -+++ new.enumConstantMembers.types -@@= skipped -78, +78 lines =@@ - >0.0 : 0 - - e = NaN, -->e : E5.d -+>e : E5.e - >NaN : number - - f = Infinity, -@@= skipped -41, +41 lines =@@ - >0.0 : 0 - - e = NaN, -->e : E6.d -+>e : E6.e - >NaN : number - - f = Infinity, \ No newline at end of file diff --git a/testdata/tests/cases/compiler/enumNaNValues.ts b/testdata/tests/cases/compiler/enumNaNValues.ts new file mode 100644 index 00000000000..33c0d037412 --- /dev/null +++ b/testdata/tests/cases/compiler/enumNaNValues.ts @@ -0,0 +1,15 @@ +// @strict: true + +enum E { + A = NaN, + B = NaN, +} + +const a: E.A = E.B; +const b: E.B = E.A; + +enum F { + X = NaN, +} + +const c: E.A = F.X; // Error expected - different enums