Skip to content

Commit 6a42cad

Browse files
authored
Merge pull request #1057 from nevkontakte/wip-go1.17
Complete support for slice-to-array conversion.
2 parents 5be4748 + 54dda39 commit 6a42cad

File tree

10 files changed

+195
-29
lines changed

10 files changed

+195
-29
lines changed

compiler/expressions.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
199199

200200
switch t.Underlying().(type) {
201201
case *types.Struct, *types.Array:
202+
// JavaScript's pass-by-reference semantics makes passing array's or
203+
// struct's object semantically equivalent to passing a pointer
204+
// TODO(nevkontakte): Evaluate if performance gain justifies complexity
205+
// introduced by the special case.
202206
return fc.translateExpr(e.X)
203207
}
204208

@@ -446,11 +450,22 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
446450

447451
case *ast.IndexExpr:
448452
switch t := fc.pkgCtx.TypeOf(e.X).Underlying().(type) {
449-
case *types.Array, *types.Pointer:
453+
case *types.Pointer:
454+
if _, ok := t.Elem().Underlying().(*types.Array); !ok {
455+
// Should never happen in type-checked code.
456+
panic(fmt.Errorf("non-array pointers can't be used with index expression"))
457+
}
458+
// Rewrite arrPtr[i] → (*arrPtr)[i] to concentrate array dereferencing
459+
// logic in one place.
460+
x := &ast.StarExpr{
461+
Star: e.X.Pos(),
462+
X: e.X,
463+
}
464+
astutil.SetType(fc.pkgCtx.Info.Info, t.Elem(), x)
465+
e.X = x
466+
return fc.translateExpr(e)
467+
case *types.Array:
450468
pattern := rangeCheck("%1e[%2f]", fc.pkgCtx.Types[e.Index].Value != nil, true)
451-
if _, ok := t.(*types.Pointer); ok { // check pointer for nix (attribute getter causes a panic)
452-
pattern = `(%1e.nilCheck, ` + pattern + `)`
453-
}
454469
return fc.formatExpr(pattern, e.X, e.Index)
455470
case *types.Slice:
456471
return fc.formatExpr(rangeCheck("%1e.$array[%1e.$offset + %2f]", fc.pkgCtx.Types[e.Index].Value != nil, false), e.X, e.Index)
@@ -819,6 +834,7 @@ func (fc *funcContext) makeReceiver(e *ast.SelectorExpr) *expression {
819834

820835
recv := fc.translateImplicitConversionWithCloning(x, methodsRecvType)
821836
if isWrapped(recvType) {
837+
// Wrap JS-native value to have access to the Go type's methods.
822838
recv = fc.formatExpr("new %s(%s)", fc.typeName(methodsRecvType), recv)
823839
}
824840
return recv
@@ -1074,8 +1090,6 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type
10741090
switch ptrElType := t.Elem().Underlying().(type) {
10751091
case *types.Array: // (*[N]T)(expr) — converting expr to a pointer to an array.
10761092
if _, ok := exprType.Underlying().(*types.Slice); ok {
1077-
// GopherJS interprets pointer to an array as the array object itself
1078-
// due to its reference semantics, so the bellow coversion is correct.
10791093
return fc.formatExpr("$sliceToGoArray(%e, %s)", expr, fc.typeName(desiredType))
10801094
}
10811095
// TODO(nevkontakte): Is this just for aliased types (e.g. `type a [4]byte`)?

compiler/gopherjspkg/fs_vfsdata.go

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/natives/fs_vfsdata.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/natives/src/reflect/reflect.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -747,16 +747,22 @@ func cvtDirect(v Value, typ Type) Value {
747747
slice.Set("$capacity", srcVal.Get("$capacity"))
748748
val = js.Global.Call("$newDataPointer", slice, jsType(PtrTo(typ)))
749749
case Ptr:
750-
if typ.Elem().Kind() == Struct {
750+
switch typ.Elem().Kind() {
751+
case Struct:
751752
if typ.Elem() == v.typ.Elem() {
752753
val = srcVal
753754
break
754755
}
755756
val = jsType(typ).New()
756757
copyStruct(val, srcVal, typ.Elem())
757-
break
758+
case Array:
759+
// Unlike other pointers, array pointers are "wrapped" types (see
760+
// isWrapped() in the compiler package), and are represented by a native
761+
// javascript array object here.
762+
val = srcVal
763+
default:
764+
val = jsType(typ).New(srcVal.Get("$get"), srcVal.Get("$set"))
758765
}
759-
val = jsType(typ).New(srcVal.Get("$get"), srcVal.Get("$set"))
760766
case Struct:
761767
val = jsType(typ).Get("ptr").New()
762768
copyStruct(val, srcVal, typ)
@@ -1264,6 +1270,28 @@ func getJsTag(tag string) string {
12641270
return ""
12651271
}
12661272

1273+
// CanConvert reports whether the value v can be converted to type t. If
1274+
// v.CanConvert(t) returns true then v.Convert(t) will not panic.
1275+
//
1276+
// TODO(nevkontakte): this overlay can be removed after
1277+
// https://github.com/golang/go/pull/48346 is in the lastest stable Go release.
1278+
func (v Value) CanConvert(t Type) bool {
1279+
vt := v.Type()
1280+
if !vt.ConvertibleTo(t) {
1281+
return false
1282+
}
1283+
// Currently the only conversion that is OK in terms of type
1284+
// but that can panic depending on the value is converting
1285+
// from slice to pointer-to-array.
1286+
if vt.Kind() == Slice && t.Kind() == Ptr && t.Elem().Kind() == Array {
1287+
n := t.Elem().Len()
1288+
if n > v.Len() { // Avoiding use of unsafeheader.Slice here.
1289+
return false
1290+
}
1291+
}
1292+
return true
1293+
}
1294+
12671295
func (v Value) Index(i int) Value {
12681296
switch k := v.kind(); k {
12691297
case Array:

compiler/package.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,13 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor
502502
case *types.Basic, *types.Array, *types.Slice, *types.Chan, *types.Signature, *types.Interface, *types.Pointer, *types.Map:
503503
size = sizes32.Sizeof(t)
504504
}
505+
if tPointer, ok := o.Type().Underlying().(*types.Pointer); ok {
506+
if _, ok := tPointer.Elem().Underlying().(*types.Array); ok {
507+
// Array pointers have non-default constructors to support wrapping
508+
// of the native objects.
509+
constructor = "$arrayPtrCtor()"
510+
}
511+
}
505512
funcCtx.Printf(`%s = $newType(%d, %s, "%s.%s", %t, "%s", %t, %s);`, lhs, size, typeKind(o.Type()), o.Pkg().Name(), o.Name(), o.Name() != "", o.Pkg().Path(), o.Exported(), constructor)
506513
})
507514
d.MethodListCode = funcCtx.CatchOutput(0, func() {
@@ -754,7 +761,7 @@ func translateFunction(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt,
754761
if recv != nil && !isBlank(recv) {
755762
this := "this"
756763
if isWrapped(c.pkgCtx.TypeOf(recv)) {
757-
this = "this.$val"
764+
this = "this.$val" // Unwrap receiver value.
758765
}
759766
c.Printf("%s = %s;", c.translateExpr(recv), this)
760767
}

compiler/prelude/prelude.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ var $sliceToNativeArray = function(slice) {
154154
};
155155
156156
// Convert Go slice to a pointer to an underlying Go array.
157+
//
158+
// Note that an array pointer can be represented by an "unwrapped" native array
159+
// type, and it will be wrapped back into its Go type when necessary.
157160
var $sliceToGoArray = function(slice, arrayPtrType) {
158161
var arrayType = arrayPtrType.elem;
159162
if (arrayType !== undefined && slice.$length < arrayType.len) {

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.

compiler/prelude/types.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ var $idKey = function(x) {
6161
return String(x.$id);
6262
};
6363
64+
// Creates constructor functions for array pointer types. Returns a new function
65+
// instace each time to make sure each type is independent of the other.
66+
var $arrayPtrCtor = function() {
67+
return function(array) {
68+
this.$get = function() { return array; };
69+
this.$set = function(v) { typ.copy(this, v); };
70+
this.$val = array;
71+
}
72+
}
73+
6474
var $newType = function(size, kind, string, named, pkg, exported, constructor) {
6575
var typ;
6676
switch(kind) {
@@ -132,11 +142,7 @@ var $newType = function(size, kind, string, named, pkg, exported, constructor) {
132142
case $kindArray:
133143
typ = function(v) { this.$val = v; };
134144
typ.wrapped = true;
135-
typ.ptr = $newType(4, $kindPtr, "*" + string, false, "", false, function(array) {
136-
this.$get = function() { return array; };
137-
this.$set = function(v) { typ.copy(this, v); };
138-
this.$val = array;
139-
});
145+
typ.ptr = $newType(4, $kindPtr, "*" + string, false, "", false, $arrayPtrCtor());
140146
typ.init = function(elem, len) {
141147
typ.elem = elem;
142148
typ.len = len;

compiler/utils.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,36 @@ func isBlank(expr ast.Expr) bool {
492492
return false
493493
}
494494

495+
// isWrapped returns true for types that may need to be boxed to access full
496+
// functionality of the Go type.
497+
//
498+
// For efficiency or interoperability reasons certain Go types can be represented
499+
// by JavaScript values that weren't constructed by the corresponding Go type
500+
// constructor.
501+
//
502+
// For example, consider a Go type:
503+
//
504+
// type SecretInt int
505+
// func (_ SecretInt) String() string { return "<secret>" }
506+
//
507+
// func main() {
508+
// var i SecretInt = 1
509+
// println(i.String())
510+
// }
511+
//
512+
// For this example the compiler will generate code similar to the snippet below:
513+
//
514+
// SecretInt = $pkg.SecretInt = $newType(4, $kindInt, "main.SecretInt", true, "main", true, null);
515+
// SecretInt.prototype.String = function() {
516+
// return "<secret>";
517+
// };
518+
// main = function() {
519+
// var i = 1;
520+
// console.log(new SecretInt(i).String());
521+
// };
522+
//
523+
// Note that the generated code assigns a primitive "number" value into i, and
524+
// only boxes it into an object when it's necessary to access its methods.
495525
func isWrapped(ty types.Type) bool {
496526
switch t := ty.Underlying().(type) {
497527
case *types.Basic:

tests/arrays_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package tests
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestArrayPointer(t *testing.T) {
9+
t.Run("nil", func(t *testing.T) {
10+
var p1 *[1]int
11+
if p1 != nil {
12+
t.Errorf("Zero-value array pointer is not equal to nil: %v", p1)
13+
}
14+
15+
var p2 *[1]int = nil
16+
if p2 != nil {
17+
t.Errorf("Nil array pointer is not equal to nil: %v", p2)
18+
}
19+
20+
p3 := func() *[1]int { return nil }()
21+
if p3 != nil {
22+
t.Errorf("Nil array pointer returned from function is not equal to nil: %v", p3)
23+
}
24+
25+
if p1 != p3 || p1 != p2 || p2 != p3 {
26+
t.Errorf("Nil pointers are not equal to each other: %v %v %v", p1, p2, p3)
27+
}
28+
29+
if v := reflect.ValueOf(p1); !v.IsNil() {
30+
t.Errorf("reflect.Value.IsNil() is false for a nil pointer: %v %v", p1, v)
31+
}
32+
33+
type arr *[1]int
34+
var p4 arr = nil
35+
36+
if v := reflect.ValueOf(p4); !v.IsNil() {
37+
t.Errorf("reflect.Value.IsNil() is false for a nil pointer: %v %v", p4, v)
38+
}
39+
})
40+
41+
t.Run("pointer-dereference", func(t *testing.T) {
42+
a1 := [1]int{42}
43+
aPtr := &a1
44+
a2 := *aPtr
45+
if !reflect.DeepEqual(a1, a2) {
46+
t.Errorf("Array after pointer dereferencing is not equal to the original: %v != %v", a1, a2)
47+
t.Logf("Pointer: %v", aPtr)
48+
}
49+
})
50+
51+
t.Run("interface-and-back", func(t *testing.T) {
52+
type arr *[1]int
53+
tests := []struct {
54+
name string
55+
a arr
56+
}{{
57+
name: "not nil",
58+
a: &[1]int{42},
59+
}, {
60+
name: "nil",
61+
a: nil,
62+
}}
63+
for _, test := range tests {
64+
a1 := test.a
65+
i := interface{}(a1)
66+
a2 := i.(arr)
67+
68+
if a1 != a2 {
69+
t.Errorf("Array pointer is not equal to itself after interface conversion: %v != %v", a1, a2)
70+
println(a1, a2)
71+
}
72+
}
73+
})
74+
75+
t.Run("reflect.IsNil", func(t *testing.T) {
76+
77+
})
78+
}

0 commit comments

Comments
 (0)