diff --git a/build/build.go b/build/build.go index 2cf6a3fec..42b979846 100644 --- a/build/build.go +++ b/build/build.go @@ -174,7 +174,6 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke overrides := make(map[string]overrideInfo) for _, file := range overlayFiles { augmentOverlayFile(file, overrides) - pruneImports(file) } delete(overrides, "init") diff --git a/compiler/expressions.go b/compiler/expressions.go index 622101997..3b9211f71 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -387,7 +387,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { switch basic.Kind() { case types.Int32, types.Int: return fc.formatParenExpr("$imul(%e, %e)", e.X, e.Y) - case types.Uint32, types.Uintptr: + case types.Uint32, types.Uint, types.Uintptr: return fc.formatParenExpr("$imul(%e, %e) >>> 0", e.X, e.Y) } return fc.fixNumber(fc.formatExpr("%e * %e", e.X, e.Y), basic) @@ -1131,8 +1131,6 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type return fc.fixNumber(fc.formatParenExpr("%1l + ((%1h >> 31) * 4294967296)", expr), t) } return fc.fixNumber(fc.formatExpr("%s.$low", fc.translateExpr(expr)), t) - case isFloat(basicExprType): - return fc.formatParenExpr("%e >> 0", expr) case types.Identical(exprType, types.Typ[types.UnsafePointer]): return fc.translateExpr(expr) default: diff --git a/compiler/prelude/jsmapping.js b/compiler/prelude/jsmapping.js index b22454bc3..f5317d626 100644 --- a/compiler/prelude/jsmapping.js +++ b/compiler/prelude/jsmapping.js @@ -236,6 +236,9 @@ var $internalize = (v, t, recv, seen, makeWrapper) => { case $kindFloat64: return parseFloat(v); case $kindArray: + if (v === null || v === undefined) { + $throwRuntimeError("cannot internalize "+v+" as a "+t.string); + } if (v.length !== t.len) { $throwRuntimeError("got array with wrong size from JavaScript native"); } @@ -331,6 +334,9 @@ var $internalize = (v, t, recv, seen, makeWrapper) => { return $internalize(v, t.elem, makeWrapper); } case $kindSlice: + if (v == null) { + return t.zero(); + } return new t($mapArray(v, e => { return $internalize(e, t.elem, makeWrapper); })); case $kindString: v = String(v); diff --git a/tests/js_test.go b/tests/js_test.go index ebebeca2a..2d67fb99a 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -830,6 +830,45 @@ func TestExternalize(t *testing.T) { } } +func TestInternalizeSlice(t *testing.T) { + tests := []struct { + name string + init []int + want string + }{ + { + name: `nil slice`, + init: []int(nil), + want: `[]int(nil)`, + }, + { + name: `empty slice`, + init: []int{}, + want: `[]int{}`, + }, + { + name: `non-empty slice`, + init: []int{42, 53, 64}, + want: `[]int{42, 53, 64}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := struct { + *js.Object + V []int `js:"V"` // V is externalized + }{Object: js.Global.Get("Object").New()} + b.V = tt.init + + result := fmt.Sprintf(`%#v`, b.V) // internalize b.V + if result != tt.want { + t.Errorf(`Unexpected result %q != %q`, result, tt.want) + } + }) + } +} + func TestInternalizeExternalizeNull(t *testing.T) { type S struct { *js.Object diff --git a/tests/numeric_test.go b/tests/numeric_test.go index df06db584..484968f41 100644 --- a/tests/numeric_test.go +++ b/tests/numeric_test.go @@ -1,10 +1,14 @@ package tests import ( + "fmt" + "math/bits" "math/rand" "runtime" "testing" "testing/quick" + + "github.com/gopherjs/gopherjs/js" ) // naiveMul64 performs 64-bit multiplication without using the multiplication @@ -93,3 +97,107 @@ func BenchmarkMul64(b *testing.B) { } }) } + +func TestIssue733(t *testing.T) { + if runtime.GOOS != "js" { + t.Skip("test uses GopherJS-specific features") + } + + t.Run("sign", func(t *testing.T) { + f := float64(-1) + i := uint32(f) + underlying := js.InternalObject(i).Float() // Get the raw JS number behind i. + if want := float64(4294967295); underlying != want { + t.Errorf("Got: uint32(float64(%v)) = %v. Want: %v.", f, underlying, want) + } + }) + t.Run("truncation", func(t *testing.T) { + f := float64(300) + i := uint8(f) + underlying := js.InternalObject(i).Float() // Get the raw JS number behind i. + if want := float64(44); underlying != want { + t.Errorf("Got: uint32(float64(%v)) = %v. Want: %v.", f, underlying, want) + } + }) +} + +// Test_32BitEnvironment tests that GopherJS behaves correctly +// as a 32-bit environment for integers. To simulate a 32 bit environment +// we have to use `$imul` instead of `*` to get the correct result. +func Test_32BitEnvironment(t *testing.T) { + if bits.UintSize != 32 { + t.Skip(`test is only relevant for 32-bit environment`) + } + + tests := []struct { + x, y, exp uint64 + }{ + { + x: 65535, // x = 2^16 - 1 + y: 65535, // same as x + exp: 4294836225, // x² works since it doesn't overflow 32 bits. + }, + { + x: 134217729, // x = 2^27 + 1, x < 2^32 and x > sqrt(2^53), so x² overflows 53 bits. + y: 134217729, // same as x + exp: 268435457, // x² mod 2^32 = (2^27 + 1)² mod 2^32 = (2^54 + 2^28 + 1) mod 2^32 = 2^28 + 1 + // In pure JS, `x * x >>> 0`, would result in 268,435,456 because it lost the least significant bit + // prior to being truncated, where in a real 32 bit environment, it would be 268,435,457 since + // the rollover removed the most significant bit and doesn't affect the least significant bit. + }, + { + x: 4294967295, // x = 2^32 - 1 another case where x² overflows 53 bits causing a loss of precision. + y: 4294967295, // same as x + exp: 1, // x² mod 2^32 = (2^32 - 1)² mod 2^32 = (2^64 - 2^33 + 1) mod 2^32 = 1 + // In pure JS, `x * x >>> 0`, would result in 0 because it lost the least significant bits. + }, + { + x: 4294967295, // x = 2^32 - 1 + y: 3221225473, // y = 2^31 + 2^30 + 1 + exp: 1073741823, // 2^32 - 1. + // In pure JS, `x * y >>> 0`, would result in 1,073,741,824. + }, + { + x: 4294967295, // x = 2^32 - 1 + y: 134217729, // y = 2^27 + 1 + exp: 4160749567, // In pure JS, `x * y >>> 0`, would result in 4,160,749,568. + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf(`#%d/uint32`, i), func(t *testing.T) { + x, y, exp := uint32(test.x), uint32(test.y), uint32(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + + t.Run(fmt.Sprintf(`#%d/uintptr`, i), func(t *testing.T) { + x, y, exp := uintptr(test.x), uintptr(test.y), uintptr(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + + t.Run(fmt.Sprintf(`#%d/uint`, i), func(t *testing.T) { + x, y, exp := uint(test.x), uint(test.y), uint(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + + t.Run(fmt.Sprintf(`#%d/int32`, i), func(t *testing.T) { + x, y, exp := int32(test.x), int32(test.y), int32(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + + t.Run(fmt.Sprintf(`#%d/int`, i), func(t *testing.T) { + x, y, exp := int(test.x), int(test.y), int(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + } +}