Skip to content

Commit ab5ceb8

Browse files
committed
WIP fixes for internalize and zero val translation of undefined and null
1 parent e1d10e7 commit ab5ceb8

File tree

5 files changed

+295
-36
lines changed

5 files changed

+295
-36
lines changed

compiler/expressions.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,8 +1213,6 @@ func (c *funcContext) internalize(s *expression, t types.Type) *expression {
12131213
switch u := t.Underlying().(type) {
12141214
case *types.Basic:
12151215
switch {
1216-
case isBoolean(u):
1217-
return c.formatExpr("!!(%s)", s)
12181216
case isInteger(u) && !is64Bit(u):
12191217
return c.fixNumber(c.formatExpr("$parseInt(%s)", s), u)
12201218
case isFloat(u):

compiler/prelude/jsmapping.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,13 @@ var $internalize = function(v, t, recv) {
194194
}
195195
switch (t.kind) {
196196
case $kindBool:
197-
return !!v;
197+
if (v === undefined || v == null) {
198+
return false;
199+
}
200+
if (v.constructor !== Boolean) {
201+
$throwRuntimeError("tried to internalize non-bool value of type " + $typeof(v));
202+
}
203+
return Boolean(v);
198204
case $kindInt:
199205
return parseInt(v);
200206
case $kindInt8:
@@ -253,12 +259,9 @@ var $internalize = function(v, t, recv) {
253259
if (t.methods.length !== 0) {
254260
$throwRuntimeError("cannot internalize " + t.string);
255261
}
256-
if (v === null) {
262+
if (v === null || v === undefined) {
257263
return $ifaceNil;
258264
}
259-
if (v === undefined) {
260-
return new $jsObjectPtr(undefined);
261-
}
262265
switch (v.constructor) {
263266
case Int8Array:
264267
return new ($sliceType($Int8))(v);
@@ -315,6 +318,12 @@ var $internalize = function(v, t, recv) {
315318
case $kindSlice:
316319
return new t($mapArray(v, function(e) { return $internalize(e, t.elem); }));
317320
case $kindString:
321+
if (v === undefined || v == null) {
322+
return "";
323+
}
324+
if (v.constructor !== String) {
325+
$throwRuntimeError("tried to internalize non-string value of type " + $typeof(v));
326+
}
318327
v = String(v);
319328
if ($isASCII(v)) {
320329
return v;
@@ -367,6 +376,21 @@ var $internalize = function(v, t, recv) {
367376
$throwRuntimeError("cannot internalize " + t.string);
368377
};
369378
379+
var $typeof = function(v) {
380+
if (v === undefined) {
381+
return "undefined";
382+
}
383+
384+
if (v === null) {
385+
return "null";
386+
}
387+
388+
var to = typeof v;
389+
var cn = v.constructor.name;
390+
391+
return to + "/" + cn;
392+
};
393+
370394
/* $isASCII reports whether string s contains only ASCII characters. */
371395
var $isASCII = function(s) {
372396
for (var i = 0; i < s.length; i++) {

compiler/prelude/prelude_min.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/js.go

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,60 @@
1-
// Package js provides functions for interacting with native JavaScript APIs. Calls to these functions are treated specially by GopherJS and translated directly to their corresponding JavaScript syntax.
1+
// Package js provides functions for interacting with native JavaScript APIs.
2+
// Calls to these functions are treated specially by GopherJS and translated
3+
// directly to their corresponding JavaScript syntax.
24
//
3-
// Use MakeWrapper to expose methods to JavaScript. When passing values directly, the following type conversions are performed:
5+
// Use MakeWrapper to expose methods to JavaScript.
46
//
5-
// | Go type | JavaScript type | Conversions back to interface{} |
6-
// | --------------------- | --------------------- | ------------------------------- |
7-
// | bool | Boolean | bool |
8-
// | integers and floats | Number | float64 |
9-
// | string | String | string |
10-
// | []int8 | Int8Array | []int8 |
11-
// | []int16 | Int16Array | []int16 |
12-
// | []int32, []int | Int32Array | []int |
13-
// | []uint8 | Uint8Array | []uint8 |
14-
// | []uint16 | Uint16Array | []uint16 |
15-
// | []uint32, []uint | Uint32Array | []uint |
16-
// | []float32 | Float32Array | []float32 |
17-
// | []float64 | Float64Array | []float64 |
18-
// | all other slices | Array | []interface{} |
19-
// | arrays | see slice type | see slice type |
20-
// | functions | Function | func(...interface{}) *js.Object |
21-
// | time.Time | Date | time.Time |
22-
// | - | instanceof Node | *js.Object |
23-
// | maps, structs | instanceof Object | map[string]interface{} |
7+
// Internalization
248
//
25-
// Additionally, for a struct containing a *js.Object field, only the content of the field will be passed to JavaScript and vice versa.
9+
// When values pass from Javascript to Go, a process known as internalization,
10+
// the following conversion table is applied:
11+
//
12+
// |----------------+---------------+-------------------------+--------|
13+
// | Go target type | Translation | Javascript source value | Result |
14+
// |----------------+---------------+-------------------------+--------|
15+
// | string | UTF16 -> UTF8 | null | "" |
16+
// | | | undefined | "" |
17+
// | | | "" | "" |
18+
// | | | new String("") | "" |
19+
// | | | "ok" † | "ok" |
20+
// | | | new String("ok") † | "ok" |
21+
// |----------------+---------------+-------------------------+--------|
22+
// | bool | none | null | false |
23+
// | | | undefined | false |
24+
// | | | false † | false |
25+
// | | | new Boolean(false) † | false |
26+
// |----------------+---------------+-------------------------+--------|
27+
//
28+
// Any source values not listed in this table cause a runtime panic for a given
29+
// target type if a conversion is attempted, e.g. a Javascript number value
30+
// being assigned to a string type Go variable.
31+
//
32+
// Source values annotated with † are generally applicable to all valid
33+
// values of the target type. e.g. for target type string, "ok" represents
34+
// all valid string primitive values.
35+
//
36+
// Externalization
37+
//
38+
// When values pass from Go to Javascript, a process known as externalization,
39+
// the following conversion table is applied:
40+
//
41+
// |----------------+---------------+-----------------+--------+---------+-------------|
42+
// | Go source type | Translation | Go source value | Result | typeof | constructor |
43+
// |----------------+---------------+-----------------+--------+---------+-------------|
44+
// | string | UTF8 -> UTF16 | "" | "" | string | String |
45+
// | | | "ok" † | "ok" | | |
46+
// |----------------+---------------+-----------------+--------+---------+-------------|
47+
// | bool | none | false | false | boolean | Boolean |
48+
// | | | true | true | | |
49+
// |----------------+---------------+-----------------+--------+---------+-------------|
50+
//
51+
// Source values annotated with † are generally applicable to all valid
52+
// values of the target type. e.g. for target type string, "ok" represents
53+
// all valid string values.
54+
//
55+
// Special struct types
56+
//
57+
// To follow....
2658
package js
2759

2860
// Object is a container for a native JavaScript object. Calls to its methods are treated specially by GopherJS and translated directly to their JavaScript syntax. A nil pointer to Object is equal to JavaScript's "null". Object can not be used as a map key.

js/js_test.go

Lines changed: 211 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ func TestCallWithNull(t *testing.T) {
467467
func TestReflection(t *testing.T) {
468468
o := js.Global.Call("eval", "({ answer: 42 })")
469469
if reflect.ValueOf(o).Interface().(*js.Object) != o {
470-
t.Fail()
470+
t.Fatal()
471471
}
472472

473473
type S struct {
@@ -477,18 +477,19 @@ func TestReflection(t *testing.T) {
477477

478478
v := reflect.ValueOf(&s).Elem()
479479
if v.Field(0).Interface().(*js.Object).Get("answer").Int() != 42 {
480-
t.Fail()
480+
t.Fatal()
481481
}
482482
if v.Field(0).MethodByName("Get").Call([]reflect.Value{reflect.ValueOf("answer")})[0].Interface().(*js.Object).Int() != 42 {
483-
t.Fail()
483+
t.Fatal()
484484
}
485485
v.Field(0).Set(reflect.ValueOf(js.Global.Call("eval", "({ answer: 100 })")))
486486
if s.Field.Get("answer").Int() != 100 {
487-
t.Fail()
487+
t.Fatal()
488488
}
489489

490-
if fmt.Sprintf("%+v", s) != "{Field:[object Object]}" {
491-
t.Fail()
490+
expFmt := "{Field:[object Object]}"
491+
if v := fmt.Sprintf("%+v", s); v != expFmt {
492+
t.Fatalf("fmt out was: %q; expected %q", v, expFmt)
492493
}
493494
}
494495

@@ -620,3 +621,207 @@ func TestStructWithNonIdentifierJSTag(t *testing.T) {
620621
t.Errorf("value via js.Object.Get gave %q, want %q", got, want)
621622
}
622623
}
624+
625+
// Internalize is used as a helper type to test $internalize. A struct is used
626+
// in order that all of the Go types can be verified (otherwise we are limited
627+
// to the methods on *Object). Where methods on *Object exist they too will be
628+
// tested
629+
type Internalize struct {
630+
*js.Object
631+
632+
string string `js:"string"`
633+
bool bool `js:"bool"`
634+
}
635+
636+
func TestInternalizeString(t *testing.T) {
637+
fieldName := "string"
638+
zero := ""
639+
640+
s := &Internalize{Object: js.Global.Get("Object").New()}
641+
642+
// *************
643+
// undefined
644+
// *************
645+
s.Object.Set(fieldName, jsundefined())
646+
647+
// via struct field
648+
if v := s.string; v != zero {
649+
t.Fatalf("expected string field to be %q, got %q", zero, v)
650+
}
651+
652+
// via *js.Object.String()
653+
if v := s.Object.Get(fieldName).String(); v != zero {
654+
t.Fatalf("expected string field via *js.Object.String() to be %q, got %q", zero, v)
655+
}
656+
657+
// *************
658+
// null
659+
// *************
660+
s.Object.Set(fieldName, jsnull())
661+
662+
// via struct field
663+
if v := s.string; v != zero {
664+
t.Fatalf("expected string field to be %q, got %q", zero, v)
665+
}
666+
667+
// via *js.Object.String()
668+
if v := s.Object.Get(fieldName).String(); v != zero {
669+
t.Fatalf("expected string field via *js.Object.String() to be %q, got %q", zero, v)
670+
}
671+
672+
// *************
673+
// valid primitive string
674+
// *************
675+
exp := "ok"
676+
s.Object.Set(fieldName, jsval(`"`+exp+`"`))
677+
678+
// via struct field
679+
if v := s.string; v != exp {
680+
t.Fatalf("expected string field to be %q, got %q", zero, v)
681+
}
682+
683+
// via *js.Object.String()
684+
if v := s.Object.Get(fieldName).String(); v != exp {
685+
t.Fatalf("expected string field via *js.Object.String() to be %q, got %q", zero, v)
686+
}
687+
688+
// *************
689+
// valid String object
690+
// *************
691+
s.Object.Set(fieldName, jsval(`new String("`+exp+`")`))
692+
693+
// via struct field
694+
if v := s.string; v != exp {
695+
t.Fatalf("expected string field to be %q, got %q", zero, v)
696+
}
697+
698+
// via *js.Object.String()
699+
if v := s.Object.Get(fieldName).String(); v != exp {
700+
t.Fatalf("expected string field via *js.Object.String() to be %q, got %q", zero, v)
701+
}
702+
703+
// *************
704+
// invalid
705+
// *************
706+
s.Object.Set(fieldName, jsval("5"))
707+
708+
shouldRuntimePanic(t, "runtime error: tried to internalize non-string value of type number/Number", func() {
709+
_ = s.string
710+
})
711+
712+
shouldRuntimePanic(t, "runtime error: tried to internalize non-string value of type number/Number", func() {
713+
_ = s.Object.Get(fieldName).String()
714+
})
715+
}
716+
717+
func TestInternalizeBool(t *testing.T) {
718+
fieldName := "bool"
719+
zero := false
720+
721+
s := &Internalize{Object: js.Global.Get("Object").New()}
722+
723+
// *************
724+
// undefined
725+
// *************
726+
s.Object.Set(fieldName, jsundefined())
727+
728+
// via struct field
729+
if v := s.bool; v != zero {
730+
t.Fatalf("expected bool field to be %q, got %q", zero, v)
731+
}
732+
733+
// via *js.Object.Bool()
734+
if v := s.Object.Get(fieldName).Bool(); v != zero {
735+
t.Fatalf("expected bool field via *js.Object.Bool() to be %q, got %q", zero, v)
736+
}
737+
738+
// *************
739+
// null
740+
// *************
741+
s.Object.Set(fieldName, jsnull())
742+
743+
// via struct field
744+
if v := s.bool; v != zero {
745+
t.Fatalf("expected bool field to be %q, got %q", zero, v)
746+
}
747+
748+
// via *js.Object.Bool()
749+
if v := s.Object.Get(fieldName).Bool(); v != zero {
750+
t.Fatalf("expected bool field via *js.Object.Bool() to be %q, got %q", zero, v)
751+
}
752+
753+
// *************
754+
// valid primitive bool
755+
// *************
756+
exp := true
757+
s.Object.Set(fieldName, jsval(`true`))
758+
759+
// via struct field
760+
if v := s.bool; v != exp {
761+
t.Fatalf("expected bool field to be %v, got %v", exp, v)
762+
}
763+
764+
// via *js.Object.Bool()
765+
if v := s.Object.Get(fieldName).Bool(); v != exp {
766+
t.Fatalf("expected bool field via *js.Object.Bool() to be %q, got %q", zero, v)
767+
}
768+
769+
// *************
770+
// valid Bool object
771+
// *************
772+
s.Object.Set(fieldName, jsval(`new Boolean(true)`))
773+
774+
// via struct field
775+
if v := s.bool; v != exp {
776+
t.Fatalf("expected bool field to be %q, got %q", zero, v)
777+
}
778+
779+
// via *js.Object.Bool()
780+
if v := s.Object.Get(fieldName).Bool(); v != exp {
781+
t.Fatalf("expected bool field via *js.Object.Bool() to be %q, got %q", zero, v)
782+
}
783+
784+
// *************
785+
// invalid
786+
// *************
787+
s.Object.Set(fieldName, jsval("5"))
788+
789+
shouldRuntimePanic(t, "runtime error: tried to internalize non-bool value of type number/Number", func() {
790+
_ = s.bool
791+
})
792+
793+
shouldRuntimePanic(t, "runtime error: tried to internalize non-bool value of type number/Number", func() {
794+
_ = s.Object.Get(fieldName).Bool()
795+
})
796+
}
797+
798+
func jsval(v string) *js.Object {
799+
return js.Global.Call("eval", v)
800+
}
801+
802+
func jsnull() *js.Object {
803+
return js.Global.Call("eval", "null")
804+
}
805+
806+
func jsundefined() *js.Object {
807+
return js.Global.Call("eval", "undefined")
808+
}
809+
810+
func consolelog(args ...interface{}) {
811+
js.Global.Get("console").Call("log", args...)
812+
}
813+
814+
func shouldRuntimePanic(t *testing.T, msg string, f func()) {
815+
defer func() {
816+
err, ok := recover().(error)
817+
if !ok {
818+
t.Fatalf("expected to have had to handle panic; we didn't see a panic")
819+
}
820+
821+
if err.Error() != msg {
822+
t.Fatalf("expected error %q, got %q", msg, err)
823+
}
824+
}()
825+
826+
f()
827+
}

0 commit comments

Comments
 (0)