Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions internal/compiler/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/microsoft/typescript-go/internal/binder"
"github.com/microsoft/typescript-go/internal/compiler/diagnostics"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/jsnum"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/stringutil"
)
Expand Down Expand Up @@ -2172,7 +2173,7 @@ func createEvaluator(evaluateEntity Evaluator) Evaluator {
case ast.KindMinusToken:
return evaluatorResult(-value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindTildeToken:
return evaluatorResult(float64(^int32(value)), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
return evaluatorResult(jsnum.BitwiseNOT(value), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
}
}
case ast.KindBinaryExpression:
Expand All @@ -2187,17 +2188,17 @@ func createEvaluator(evaluateEntity Evaluator) Evaluator {
if leftIsNum && rightIsNum {
switch operator {
case ast.KindBarToken:
return evaluatorResult(float64(int32(leftNum)|int32(rightNum)), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
return evaluatorResult(jsnum.BitwiseOR(leftNum, rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindAmpersandToken:
return evaluatorResult(float64(int32(leftNum)&int32(rightNum)), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
return evaluatorResult(jsnum.BitwiseAND(leftNum, rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindGreaterThanGreaterThanToken:
return evaluatorResult(float64(int32(leftNum)>>int32(rightNum)), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
return evaluatorResult(jsnum.SignedRightShift(leftNum, rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindGreaterThanGreaterThanGreaterThanToken:
return evaluatorResult(float64(uint32(leftNum)>>uint32(rightNum)), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
return evaluatorResult(jsnum.UnsignedRightShift(leftNum, rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindLessThanLessThanToken:
return evaluatorResult(float64(int32(leftNum)<<int32(rightNum)), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
return evaluatorResult(jsnum.LeftShift(leftNum, rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindCaretToken:
return evaluatorResult(float64(int32(leftNum)^int32(rightNum)), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
return evaluatorResult(jsnum.BitwiseXOR(leftNum, rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindAsteriskToken:
return evaluatorResult(leftNum*rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindSlashToken:
Expand All @@ -2207,9 +2208,9 @@ func createEvaluator(evaluateEntity Evaluator) Evaluator {
case ast.KindMinusToken:
return evaluatorResult(leftNum-rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindPercentToken:
return evaluatorResult(leftNum-rightNum*math.Floor(leftNum/rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
return evaluatorResult(jsnum.Remainder(leftNum, rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
case ast.KindAsteriskAsteriskToken:
return evaluatorResult(math.Pow(leftNum, rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
return evaluatorResult(jsnum.Exponentiate(leftNum, rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences)
}
}
leftStr, leftIsStr := left.value.(string)
Expand Down
111 changes: 111 additions & 0 deletions internal/jsnum/jsnum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Package jsnum provides JS-like number handling.
package jsnum

import "math"

// https://tc39.es/ecma262/2024/multipage/abstract-operations.html#sec-touint32
func toUint32(x float64) uint32 {
// Fast path: if the number is the range (-2^31, 2^32), i.e. an SMI,
// then we don't need to do any special mapping.
if smi := int32(x); float64(smi) == x {
return uint32(smi)
}

// If the number is non-finite (NaN, +Inf, -Inf; exp=0x7FF), it maps to zero.
// This is equivalent to checking `math.IsNaN(x) || math.IsInf(x, 0)` in one operation.
const mask = 0x7FF0000000000000
if math.Float64bits(x)&mask == mask {
return 0
}

// Otherwise, take x modulo 2^32, mapping positive numbers
// to [0, 2^32) and negative numbers to (-2^32, -0.0].
x = math.Mod(x, 1<<32)

// Convert to uint32, which will wrap negative numbers.
return uint32(x)
}

// https://tc39.es/ecma262/2024/multipage/abstract-operations.html#sec-toint32
func toInt32(x float64) int32 {
// The only difference between ToUint32 and ToInt32 is the interpretation of the bits.
return int32(toUint32(x))
}

func toShiftCount(x float64) uint32 {
return toUint32(x) & 31
}

// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-signedRightShift
func SignedRightShift(x, y float64) float64 {
return float64(toInt32(x) >> toShiftCount(y))
}

// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-unsignedRightShift
func UnsignedRightShift(x, y float64) float64 {
return float64(toUint32(x) >> toShiftCount(y))
}

// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-leftShift
func LeftShift(x, y float64) float64 {
return float64(toInt32(x) << toShiftCount(y))
}

// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseNOT
func BitwiseNOT(x float64) float64 {
return float64(^toInt32(x))
}

// The below are implemented by https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numberbitwiseop.

// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseOR
func BitwiseOR(x, y float64) float64 {
return float64(toInt32(x) | toInt32(y))
}

// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseAND
func BitwiseAND(x, y float64) float64 {
return float64(toInt32(x) & toInt32(y))
}

// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseXOR
func BitwiseXOR(x, y float64) float64 {
return float64(toInt32(x) ^ toInt32(y))
}

var negativeZero = math.Copysign(0, -1)

// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-remainder
func Remainder(n, d float64) float64 {
switch {
case math.IsNaN(n) || math.IsNaN(d):
return math.NaN()
case math.IsInf(n, 0):
return math.NaN()
case math.IsInf(d, 0):
return n
case d == 0:
return math.NaN()
case n == 0:
return n
}

r := n - d*math.Trunc(n/d)
if r == 0 || n < 0 {
return negativeZero
}

return r
}

// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-exponentiate
func Exponentiate(base, exponent float64) float64 {
switch {
case (base == 1 || base == -1) && math.IsInf(exponent, 0):
return math.NaN()
case base == 1 && math.IsNaN(exponent):
return math.NaN()
}

return math.Pow(base, exponent)
}
234 changes: 234 additions & 0 deletions internal/jsnum/jsnum_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package jsnum

import (
"fmt"
"math"
"testing"

"gotest.tools/v3/assert"
)

const (
maxSafeInteger = 1<<53 - 1
minSafeInteger = -maxSafeInteger
)

var toInt32Tests = []struct {
name string
input float64
want int32
bench bool
}{
{"0.0", 0, 0, true},
{"-0.0", negativeZero, 0, false},
{"NaN", math.NaN(), 0, true},
{"+Inf", math.Inf(1), 0, true},
{"-Inf", math.Inf(-1), 0, true},
{"MaxInt32", float64(math.MaxInt32), math.MaxInt32, false},
{"MaxInt32+1", float64(int64(math.MaxInt32) + 1), math.MinInt32, true},
{"MinInt32", float64(math.MinInt32), math.MinInt32, false},
{"MinInt32-1", float64(int64(math.MinInt32) - 1), math.MaxInt32, true},
{"MIN_SAFE_INTEGER", minSafeInteger, 1, false},
{"MIN_SAFE_INTEGER-1", minSafeInteger - 1, 0, false},
{"MIN_SAFE_INTEGER+1", minSafeInteger + 1, 2, false},
{"MAX_SAFE_INTEGER", maxSafeInteger, -1, true},
{"MAX_SAFE_INTEGER-1", maxSafeInteger - 1, -2, true},
{"MAX_SAFE_INTEGER+1", maxSafeInteger + 1, 0, true},
{"-8589934590", -8589934590, 2, false},
{"0xDEADBEEF", 0xDEADBEEF, -559038737, true},
{"4294967808", 4294967808, 512, false},
{"-0.4", -0.4, 0, false},
{"SmallestNonzeroFloat64", math.SmallestNonzeroFloat64, 0, false},
{"-SmallestNonzeroFloat64", -math.SmallestNonzeroFloat64, 0, false},
{"MaxFloat64", math.MaxFloat64, 0, false},
{"-MaxFloat64", -math.MaxFloat64, 0, false},
{"Largest subnormal number", math.Float64frombits(0x000FFFFFFFFFFFFF), 0, false},
{"Smallest positive normal number", math.Float64frombits(0x0010000000000000), 0, false},
{"Largest normal number", math.MaxFloat64, 0, false},
{"-Largest normal number", -math.MaxFloat64, 0, false},
{"1.0", 1.0, 1, false},
{"-1.0", -1.0, -1, false},
{"1e308", 1e308, 0, false},
{"-1e308", -1e308, 0, false},
{"math.Pi", math.Pi, 3, false},
{"-math.Pi", -math.Pi, -3, false},
{"math.E", math.E, 2, false},
{"-math.E", -math.E, -2, false},
{"0.5", 0.5, 0, false},
{"-0.5", -0.5, 0, false},
{"0.49999999999999994", 0.49999999999999994, 0, false},
{"-0.49999999999999994", -0.49999999999999994, 0, false},
{"0.5000000000000001", 0.5000000000000001, 0, false},
{"-0.5000000000000001", -0.5000000000000001, 0, false},
{"2^31 + 0.5", 2147483648.5, -2147483648, false},
{"-2^31 - 0.5", -2147483648.5, -2147483648, false},
{"2^40", 1099511627776, 0, false},
{"-2^40", -1099511627776, 0, false},
{"TypeFlagsNarrowable", 536624127, 536624127, true},
}

func TestToInt32(t *testing.T) {
t.Parallel()

for _, test := range toInt32Tests {
t.Run(fmt.Sprintf("%s (%v)", test.name, test.input), func(t *testing.T) {
t.Parallel()
assert.Equal(t, toInt32(test.input), test.want)
})
}
}

var sink int32

func BenchmarkToInt32(b *testing.B) {
for _, test := range toInt32Tests {
if !test.bench {
continue
}

b.Run(fmt.Sprintf("%s (%v)", test.name, test.input), func(b *testing.B) {
for range b.N {
sink = toInt32(test.input)
}
})
}
}

func TestBitwiseNOT(t *testing.T) {
t.Parallel()

assert.Equal(t, BitwiseNOT(-2147483649), BitwiseNOT(2147483647))
assert.Equal(t, BitwiseNOT(-4294967296), BitwiseNOT(0))
assert.Equal(t, BitwiseNOT(-2147483648), BitwiseNOT(-2147483648))
assert.Equal(t, BitwiseNOT(-4294967296), BitwiseNOT(0))
}

func TestBitwiseAND(t *testing.T) {
t.Parallel()

assert.Equal(t, BitwiseAND(1, 0), 0.0)
assert.Equal(t, BitwiseAND(1, 1), 1.0)
}

func TestBitwiseOR(t *testing.T) {
t.Parallel()

assert.Equal(t, BitwiseOR(1, 0), 1.0)
assert.Equal(t, BitwiseOR(1, 1), 1.0)
}

func TestBitwiseXOR(t *testing.T) {
t.Parallel()

assert.Equal(t, BitwiseXOR(1, 0), 1.0)
assert.Equal(t, BitwiseXOR(1, 1), 0.0)
}

func TestSignedRightShift(t *testing.T) {
t.Parallel()

assert.Equal(t, SignedRightShift(1, 0), 1.0)
assert.Equal(t, SignedRightShift(1, 1), 0.0)
assert.Equal(t, SignedRightShift(1, 2), 0.0)
assert.Equal(t, SignedRightShift(1, 31), 0.0)
assert.Equal(t, SignedRightShift(1, 32), 1.0)

assert.Equal(t, SignedRightShift(-4, 0), -4.0)
assert.Equal(t, SignedRightShift(-4, 1), -2.0)
assert.Equal(t, SignedRightShift(-4, 2), -1.0)
assert.Equal(t, SignedRightShift(-4, 3), -1.0)
assert.Equal(t, SignedRightShift(-4, 4), -1.0)
assert.Equal(t, SignedRightShift(-4, 31), -1.0)
assert.Equal(t, SignedRightShift(-4, 32), -4.0)
assert.Equal(t, SignedRightShift(-4, 33), -2.0)
}

func TestUnsignedRightShift(t *testing.T) {
t.Parallel()

assert.Equal(t, UnsignedRightShift(1, 0), 1.0)
assert.Equal(t, UnsignedRightShift(1, 1), 0.0)
assert.Equal(t, UnsignedRightShift(1, 2), 0.0)
assert.Equal(t, UnsignedRightShift(1, 31), 0.0)
assert.Equal(t, UnsignedRightShift(1, 32), 1.0)

assert.Equal(t, UnsignedRightShift(-4, 0), 4294967292.0)
assert.Equal(t, UnsignedRightShift(-4, 1), 2147483646.0)
assert.Equal(t, UnsignedRightShift(-4, 2), 1073741823.0)
assert.Equal(t, UnsignedRightShift(-4, 3), 536870911.0)
assert.Equal(t, UnsignedRightShift(-4, 4), 268435455.0)
assert.Equal(t, UnsignedRightShift(-4, 31), 1.0)
assert.Equal(t, UnsignedRightShift(-4, 32), 4294967292.0)
assert.Equal(t, UnsignedRightShift(-4, 33), 2147483646.0)
}

func TestLeftShift(t *testing.T) {
t.Parallel()

assert.Equal(t, LeftShift(1, 0), 1.0)
assert.Equal(t, LeftShift(1, 1), 2.0)
assert.Equal(t, LeftShift(1, 2), 4.0)
assert.Equal(t, LeftShift(1, 31), -2147483648.0)
assert.Equal(t, LeftShift(1, 32), 1.0)

assert.Equal(t, LeftShift(-4, 0), -4.0)
assert.Equal(t, LeftShift(-4, 1), -8.0)
assert.Equal(t, LeftShift(-4, 2), -16.0)
assert.Equal(t, LeftShift(-4, 3), -32.0)
assert.Equal(t, LeftShift(-4, 31), 0.0)
assert.Equal(t, LeftShift(-4, 32), -4.0)
}

func TestRemainder(t *testing.T) {
t.Parallel()

assert.Assert(t, math.IsNaN(Remainder(math.NaN(), 1)))
assert.Assert(t, math.IsNaN(Remainder(1, math.NaN())))

assert.Assert(t, math.IsNaN(Remainder(math.Inf(1), 1)))
assert.Assert(t, math.IsNaN(Remainder(math.Inf(-1), 1)))

assert.Equal(t, Remainder(123, math.Inf(1)), 123.0)
assert.Equal(t, Remainder(123, math.Inf(-1)), 123.0)

assert.Assert(t, math.IsNaN(Remainder(123, 0)))
assert.Assert(t, math.IsNaN(Remainder(123, negativeZero)))

assert.Equal(t, Remainder(0, 123), 0.0)
assert.Equal(t, Remainder(negativeZero, 123), negativeZero)
}

func TestExponentiate(t *testing.T) {
t.Parallel()

assert.Equal(t, Exponentiate(2, 3), 8.0)

assert.Equal(t, Exponentiate(math.Inf(1), 3), math.Inf(1))
assert.Equal(t, Exponentiate(math.Inf(1), -5), 0.0)

assert.Equal(t, Exponentiate(math.Inf(-1), 3), math.Inf(-1))
assert.Equal(t, Exponentiate(math.Inf(-1), 4), math.Inf(1))
assert.Equal(t, Exponentiate(math.Inf(-1), -3), negativeZero)
assert.Equal(t, Exponentiate(math.Inf(-1), -4), 0.0)

assert.Equal(t, Exponentiate(0, 3), 0.0)
assert.Equal(t, Exponentiate(0, -10), math.Inf(1))

assert.Equal(t, Exponentiate(negativeZero, 3), negativeZero)
assert.Equal(t, Exponentiate(negativeZero, 4), 0.0)
assert.Equal(t, Exponentiate(negativeZero, -3), math.Inf(-1))
assert.Equal(t, Exponentiate(negativeZero, -4), math.Inf(1))

assert.Equal(t, Exponentiate(3, math.Inf(1)), math.Inf(1))
assert.Equal(t, Exponentiate(-3, math.Inf(1)), math.Inf(1))

assert.Equal(t, Exponentiate(3, math.Inf(-1)), 0.0)
assert.Equal(t, Exponentiate(-3, math.Inf(-1)), 0.0)

assert.Assert(t, math.IsNaN(Exponentiate(math.NaN(), 3)))
assert.Assert(t, math.IsNaN(Exponentiate(1, math.Inf(1))))
assert.Assert(t, math.IsNaN(Exponentiate(1, math.Inf(-1))))
assert.Assert(t, math.IsNaN(Exponentiate(-1, math.Inf(1))))
assert.Assert(t, math.IsNaN(Exponentiate(-1, math.Inf(-1))))
assert.Assert(t, math.IsNaN(Exponentiate(1, math.NaN())))
}
Loading