From f18e89471fd78cebeac118d5afaf58b86471ef8e Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Sat, 28 Oct 2023 21:04:47 +0100 Subject: [PATCH 01/10] Implement type param conversion support for slices. --- compiler/prelude/types.js | 31 ++++++++++++++++++++++++++++- tests/typeparams/conversion_test.go | 26 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 362921a61..9b9d9ee06 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -456,8 +456,10 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindInterface: typ.convertFrom = (src) => $convertToInterface(src, typ); break; - case $kindArray: case $kindSlice: + typ.convertFrom = (src) => $convertToSlice(src, typ); + break; + case $kindArray: case $kindMap: case $kindChan: case $kindPtr: @@ -1093,4 +1095,31 @@ const $convertToBool = (src, dstType) => { */ const $convertToInterface = (src, dstType) => { return src; +}; + +/** + * Convert any type to a slice value. + * + * dstType.kind must be $kindSlice. For wrapped types, src value must be wrapped. + * The returned value is always a slice type. + */ +const $convertToSlice = (src, dstType) => { + const srcType = src.constructor; + if (srcType === dstType) { + return src; + } + + switch (srcType.kind) { + case $kindString: + if (dstType.elem.kind === $kindInt32) { // Runes are int32. + return new dstType($stringToRunes(src.$val)); + } else if (dstType.elem.kind === $kindUint8) { // Bytes are uint8. + return new dstType($stringToBytes(src.$val)); + } + break; + case $kindSlice: + return $convertSliceType(src, dstType); + break; + } + throw new Error(`Unsupported conversion from ${srcType.string} to ${dstType.string}`); }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index b59d5cd6c..2e0483f9f 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -159,6 +159,24 @@ func (tc interfaceConversion[srcType]) Run(t *testing.T) { } } +type sliceConversion[elType any, srcType ~[]elType, dstType ~[]elType] struct { + src srcType + want dstType +} + +func (tc sliceConversion[elType, srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + +type stringToSliceConversion[dstType []byte | []rune] struct { + src string + want dstType +} + +func (tc stringToSliceConversion[dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + func TestConversion(t *testing.T) { type i64 int64 type i32 int32 @@ -172,6 +190,7 @@ func TestConversion(t *testing.T) { s string i int } + type sl []byte tests := []conversionTest{ // $convertToInt64 @@ -243,6 +262,13 @@ func TestConversion(t *testing.T) { interfaceConversion[error]{src: fmt.Errorf("test error")}, interfaceConversion[*js.Object]{src: js.Global}, interfaceConversion[*int]{src: func(i int) *int { return &i }(1)}, + // $convertToSlice + sliceConversion[byte, []byte, sl]{src: []byte{1, 2, 3}, want: sl{1, 2, 3}}, + sliceConversion[byte, sl, []byte]{src: sl{1, 2, 3}, want: []byte{1, 2, 3}}, + sliceConversion[byte, []byte, sl]{src: []byte(nil), want: sl(nil)}, + sliceConversion[byte, sl, []byte]{src: sl(nil), want: []byte(nil)}, + stringToSliceConversion[[]byte]{src: "🐞", want: []byte{240, 159, 144, 158}}, + stringToSliceConversion[[]rune]{src: "🐞x", want: []rune{'🐞', 'x'}}, } for _, test := range tests { From e436c07bca7eaeb06e1193e51682268aa392a9ff Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Mon, 30 Oct 2023 17:41:11 +0000 Subject: [PATCH 02/10] Implement type param conversion to pointers. Broadly, this covers two cases: - Slice to pointer-to-array conversions. - Conversion between pointers to types with identical underlying types. Arrays are a special case because pointer to array is a wrapped type, unlike all other pointer types. Structs are a special case as well because pointer to struct is actually represented by the same underlying object as the struct value. --- compiler/prelude/types.js | 48 ++++++++++++++++++++++--- tests/gorepo/run.go | 1 - tests/typeparams/conversion_test.go | 55 +++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 5 deletions(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 9b9d9ee06..e4feb97f7 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -59,7 +59,7 @@ var $idKey = x => { }; // Creates constructor functions for array pointer types. Returns a new function -// instace each time to make sure each type is independent of the other. +// instance each time to make sure each type is independent of the other. var $arrayPtrCtor = () => { return function (array) { this.$get = () => { return array; }; @@ -224,7 +224,10 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.keyFor = $idKey; typ.init = elem => { typ.elem = elem; - typ.wrapped = (elem.kind === $kindArray); + if (elem.kind === $kindArray) { + typ.wrapped = true; + typ.wrap = (v) => ((v === typ.nil) ? v : new typ(v)); + } typ.nil = new typ($throwNilPointerError, $throwNilPointerError); }; break; @@ -459,10 +462,12 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { case $kindSlice: typ.convertFrom = (src) => $convertToSlice(src, typ); break; + case $kindPtr: + typ.convertFrom = (src) => $convertToPointer(src, typ); + break; case $kindArray: case $kindMap: case $kindChan: - case $kindPtr: case $kindFunc: case $kindStruct: break; @@ -1098,7 +1103,7 @@ const $convertToInterface = (src, dstType) => { }; /** - * Convert any type to a slice value. + * Convert to a slice value. * * dstType.kind must be $kindSlice. For wrapped types, src value must be wrapped. * The returned value is always a slice type. @@ -1122,4 +1127,39 @@ const $convertToSlice = (src, dstType) => { break; } throw new Error(`Unsupported conversion from ${srcType.string} to ${dstType.string}`); +}; + +/** +* Convert to a pointer value. +* +* dstType.kind must be $kindPtr. For wrapped types (specifically, pointers +* to an array), src value must be wrapped. The returned value is a bare JS +* array (typed or untyped), or a pointer object. +*/ +const $convertToPointer = (src, dstType) => { + const srcType = src.constructor; + + if (srcType === dstType) { + return src; + } + + // []T → *[N]T + if (srcType.kind == $kindSlice && dstType.elem.kind == $kindArray) { + return $sliceToGoArray(src, dstType); + } + + if (src === srcType.nil) { + return dstType.nil; + } + + switch (dstType.elem.kind) { + case $kindArray: + // Pointers to arrays are a wrapped type, represented by a native JS array, + // which we return directly. + return src.$val; + case $kindStruct: + return $pointerOfStructConversion(src, dstType); + default: + return new dstType(src.$get, src.$set, src.$target); + } }; \ No newline at end of file diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index cd4ab344c..c118546fc 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -173,7 +173,6 @@ var knownFails = map[string]failReason{ "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, - "typeparam/issue50833.go": {category: generics, desc: "unsupported conversion from *main.S to main.PS"}, "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 2e0483f9f..dc9e40c67 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -5,6 +5,7 @@ import ( "math" "reflect" "runtime" + "strings" "testing" "github.com/gopherjs/gopherjs/js" @@ -177,6 +178,40 @@ func (tc stringToSliceConversion[dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } +type sliceToArrayPtrConversion[elType any, srcType ~[]elType, dstType ~*[3]elType | ~*[0]elType] struct { + src srcType + want dstType + wantPanic string +} + +func (tc sliceToArrayPtrConversion[elType, srcType, dstType]) Run(t *testing.T) { + if tc.wantPanic == "" { + checkConversion(t, tc.src, dstType(tc.src), tc.want) + return + } + + var got dstType + defer func() { + err := recover() + if err == nil { + t.Fatalf("Got: %T(%v) = %v. Want: panic.", got, tc.src, got) + } + if msg := fmt.Sprint(err); !strings.Contains(msg, tc.wantPanic) { + t.Fatalf("Got panic: %v. Want: panic containing %q.", err, tc.wantPanic) + } + }() + got = dstType(tc.src) +} + +type ptrConversion[T any, srcType ~*T, dstType ~*T] struct { + src srcType + want dstType +} + +func (tc ptrConversion[T, srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + func TestConversion(t *testing.T) { type i64 int64 type i32 int32 @@ -185,12 +220,20 @@ func TestConversion(t *testing.T) { type c64 complex64 type c128 complex128 type str string + type strPtr *string type b bool type st struct { s string i int } + type stPtr *st type sl []byte + type arr [3]byte + type arrPtr *[3]byte + + strVar := "abc" + stVar := st{s: "abc", i: 42} + arrVal := [3]byte{1, 2, 3} tests := []conversionTest{ // $convertToInt64 @@ -269,6 +312,18 @@ func TestConversion(t *testing.T) { sliceConversion[byte, sl, []byte]{src: sl(nil), want: []byte(nil)}, stringToSliceConversion[[]byte]{src: "🐞", want: []byte{240, 159, 144, 158}}, stringToSliceConversion[[]rune]{src: "🐞x", want: []rune{'🐞', 'x'}}, + // $convertToPointer + sliceToArrayPtrConversion[byte, []byte, *[3]byte]{src: []byte{1, 2, 3}, want: &[3]byte{1, 2, 3}}, + sliceToArrayPtrConversion[byte, sl, arrPtr]{src: []byte{1, 2, 3}, want: arrPtr(&[3]byte{1, 2, 3})}, + sliceToArrayPtrConversion[byte, []byte, *[0]byte]{src: nil, want: nil}, + sliceToArrayPtrConversion[byte, []byte, *[3]byte]{src: []byte{1, 2}, wantPanic: "length"}, + sliceToArrayPtrConversion[byte, []byte, *[3]byte]{src: nil, wantPanic: "length"}, + ptrConversion[string, *string, strPtr]{src: &strVar, want: strPtr(&strVar)}, + ptrConversion[string, *string, strPtr]{src: nil, want: nil}, + ptrConversion[[3]byte, *[3]byte, arrPtr]{src: &arrVal, want: arrPtr(&arrVal)}, + ptrConversion[[3]byte, *[3]byte, arrPtr]{src: nil, want: nil}, + ptrConversion[st, *st, stPtr]{src: &stVar, want: stPtr(&stVar)}, + ptrConversion[st, *st, stPtr]{src: nil, want: nil}, } for _, test := range tests { From 772d85d0716bcb22a3485d3d645f41822abd5f52 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 19:13:00 +0000 Subject: [PATCH 03/10] Implement type param conversion to structs and arrays. Since in Go they are passed by value, this is essentially equivalent to copying the value. Even when the type is the same, the copy operation is needed. --- compiler/prelude/types.js | 30 +++++++++++- tests/typeparams/conversion_test.go | 71 +++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index e4feb97f7..28efa00b7 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -466,10 +466,14 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.convertFrom = (src) => $convertToPointer(src, typ); break; case $kindArray: + typ.convertFrom = (src) => $convertToArray(src, typ); + break; + case $kindStruct: + typ.convertFrom = (src) => $convertToStruct(src, typ); + break; case $kindMap: case $kindChan: case $kindFunc: - case $kindStruct: break; default: $panic(new $String("invalid kind: " + kind)); @@ -1162,4 +1166,28 @@ const $convertToPointer = (src, dstType) => { default: return new dstType(src.$get, src.$set, src.$target); } +}; + +/** + * Convert to struct types. + * + * dstType.kind must be $kindStruct. Src must be a wrapped struct value. Returned + * value will always be a bare JavaScript object representing the struct. + */ +const $convertToStruct = (src, dstType) => { + // Since structs are passed by value, the conversion result must be a copy + // of the original value, even if it is the same type. + return $clone(src.$val, dstType); +}; + +/** + * Convert to array types. + * + * dstType.kind must be $kindArray. Src must be a wrapped array value. Returned + * value will always be a bare JavaScript object representing the array. + */ +const $convertToArray = (src, dstType) => { + // Since arrays are passed by value, the conversion result must be a copy + // of the original value, even if it is the same type. + return $clone(src.$val, dstType); }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index dc9e40c67..8d7f7db52 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -37,6 +37,27 @@ type conversionTest interface { Run(t *testing.T) } +type ( // Named types for use in conversion test cases. + i64 int64 + i32 int32 + f64 float64 + f32 float32 + c64 complex64 + c128 complex128 + str string + strPtr *string + b bool + st struct { + s string + i int + } + st2 st + stPtr *st + sl []byte + arr [3]byte + arrPtr *[3]byte +) + type numeric interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | @@ -212,25 +233,31 @@ func (tc ptrConversion[T, srcType, dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } -func TestConversion(t *testing.T) { - type i64 int64 - type i32 int32 - type f64 float64 - type f32 float32 - type c64 complex64 - type c128 complex128 - type str string - type strPtr *string - type b bool - type st struct { - s string - i int - } - type stPtr *st - type sl []byte - type arr [3]byte - type arrPtr *[3]byte +type structConversion[srcType ~struct { + s string + i int +}, dstType ~struct { + s string + i int +}] struct { + src srcType + want dstType +} +func (tc structConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + +type arrayConversion[srcType ~[3]byte, dstType ~[3]byte] struct { + src srcType + want dstType +} + +func (tc arrayConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + +func TestConversion(t *testing.T) { strVar := "abc" stVar := st{s: "abc", i: 42} arrVal := [3]byte{1, 2, 3} @@ -324,6 +351,14 @@ func TestConversion(t *testing.T) { ptrConversion[[3]byte, *[3]byte, arrPtr]{src: nil, want: nil}, ptrConversion[st, *st, stPtr]{src: &stVar, want: stPtr(&stVar)}, ptrConversion[st, *st, stPtr]{src: nil, want: nil}, + // $convertToStruct + structConversion[st, st2]{src: st{i: 42, s: "abc"}, want: st2{s: "abc", i: 42}}, + structConversion[st, struct { + s string + i int + }]{src: st{i: 42, s: "abc"}, want: st2{s: "abc", i: 42}}, + // $convertToArray + arrayConversion[[3]byte, arr]{src: [3]byte{1, 2, 3}, want: arr{1, 2, 3}}, } for _, test := range tests { From 07690760b5d0b4a66fd92e0d9d23e075152ae05d Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 19:34:44 +0000 Subject: [PATCH 04/10] Implement type param to map conversion. --- compiler/prelude/types.js | 12 ++++++++++++ tests/typeparams/conversion_test.go | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 28efa00b7..4fa03afdf 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -472,6 +472,8 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.convertFrom = (src) => $convertToStruct(src, typ); break; case $kindMap: + typ.convertFrom = (src) => $convertToMap(src, typ); + break; case $kindChan: case $kindFunc: break; @@ -1190,4 +1192,14 @@ const $convertToArray = (src, dstType) => { // Since arrays are passed by value, the conversion result must be a copy // of the original value, even if it is the same type. return $clone(src.$val, dstType); +}; + +/** + * Convert to map types. + * + * dstType.kind must be $kindMap. Src must be a wrapped map value. Returned + * value will always be a bare JavaScript object representing the map. + */ +const $convertToMap = (src, dstType) => { + return src.$val; }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 8d7f7db52..2e1b3add1 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -56,6 +56,7 @@ type ( // Named types for use in conversion test cases. sl []byte arr [3]byte arrPtr *[3]byte + m map[string]string ) type numeric interface { @@ -257,6 +258,15 @@ func (tc arrayConversion[srcType, dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } +type mapConversion[srcType ~map[string]string, dstType ~map[string]string] struct { + src srcType + want dstType +} + +func (tc mapConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + func TestConversion(t *testing.T) { strVar := "abc" stVar := st{s: "abc", i: 42} @@ -359,6 +369,9 @@ func TestConversion(t *testing.T) { }]{src: st{i: 42, s: "abc"}, want: st2{s: "abc", i: 42}}, // $convertToArray arrayConversion[[3]byte, arr]{src: [3]byte{1, 2, 3}, want: arr{1, 2, 3}}, + // $convertToMap + mapConversion[map[string]string, m]{src: map[string]string{"abc": "def"}, want: m{"abc": "def"}}, + mapConversion[map[string]string, m]{src: nil, want: nil}, } for _, test := range tests { From b66fa5779288949531caaf13f9d4e75e3e02e010 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 20:58:11 +0000 Subject: [PATCH 05/10] reflect.DeepEquals: channels of the same underlying type can be equal. According to the Go spec, channels are equal if they have been created by the same call to make(). Prior to this change reflect.DeepEqual() would return false when comparing two channels, one of this is a named type (e.g. `type ch chan string`) and was created by type conversion from the other one. After this change, DeepEquals behavior becomes consistent with the == operator. Upstream has a similar bug: https://github.com/golang/go/issues/63886. --- compiler/natives/src/reflect/reflect.go | 7 ++++++- tests/misc_test.go | 28 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index d97094e03..d35c0d85b 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -1639,7 +1639,7 @@ func DeepEqual(a1, a2 interface{}) bool { if i1 == i2 { return true } - if i1 == nil || i2 == nil || i1.Get("constructor") != i2.Get("constructor") { + if i1 == nil || i2 == nil { return false } return deepValueEqualJs(ValueOf(a1), ValueOf(a2), nil) @@ -1649,6 +1649,11 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if !v1.IsValid() || !v2.IsValid() { return !v1.IsValid() && !v2.IsValid() } + + if v1.Kind() == Chan && v2.Kind() == Chan { + return v1.object() == v2.object() + } + if v1.Type() != v2.Type() { return false } diff --git a/tests/misc_test.go b/tests/misc_test.go index a38d91c81..fed07d2a2 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -959,3 +959,31 @@ func TestFileSetSize(t *testing.T) { t.Errorf("Got: unsafe.Sizeof(token.FileSet{}) %v, Want: %v", n2, n1) } } + +func TestChanEquality(t *testing.T) { + type ch chan string + + ch1 := make(chan string) + ch2 := make(ch) + ch3 := ch(ch1) + + t.Run("equal", func(t *testing.T) { + if ch1 != ch3 { + t.Errorf("Got: ch1 != ch3. Want: channels created by the same call to make are equal.") + } + if runtime.Compiler != "gopherjs" { + t.Skip("https://github.com/golang/go/issues/63886") + } + if !reflect.DeepEqual(ch1, ch3) { + t.Errorf("Got: reflect.DeepEqual(ch1, ch3) == false. Want: channels created by the same call to make are equal.") + } + }) + t.Run("not equal", func(t *testing.T) { + if ch1 == ch2 { + t.Errorf("Got: ch1 == ch2. Want: channels created by different calls to make are not equal.") + } + if reflect.DeepEqual(ch1, ch2) { + t.Errorf("Got: reflect.DeepEqual(ch1, ch2) == true. Want: channels created by different calls to make are not equal.") + } + }) +} From 485140a520cf79b85f0821d1e38dce51db32a651 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 21:09:44 +0000 Subject: [PATCH 06/10] Implement type param to chan type conversion. --- compiler/prelude/types.js | 12 ++++++++++++ tests/typeparams/conversion_test.go | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index 4fa03afdf..df62c60ec 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -475,6 +475,8 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.convertFrom = (src) => $convertToMap(src, typ); break; case $kindChan: + typ.convertFrom = (src) => $convertToChan(src, typ); + break; case $kindFunc: break; default: @@ -1202,4 +1204,14 @@ const $convertToArray = (src, dstType) => { */ const $convertToMap = (src, dstType) => { return src.$val; +}; + +/** + * Convert to chan types. + * + * dstType.kind must be $kindChan. Src must be a wrapped chan value. Returned + * value will always be a bare $Chan object representing the channel. + */ +const $convertToChan = (src, dstType) => { + return src.$val; }; \ No newline at end of file diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 2e1b3add1..539957741 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -57,6 +57,7 @@ type ( // Named types for use in conversion test cases. arr [3]byte arrPtr *[3]byte m map[string]string + ch chan string ) type numeric interface { @@ -267,10 +268,20 @@ func (tc mapConversion[srcType, dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } +type chanConversion[srcType ~chan string, dstType ~chan string] struct { + src srcType + want dstType +} + +func (tc chanConversion[srcType, dstType]) Run(t *testing.T) { + checkConversion(t, tc.src, dstType(tc.src), tc.want) +} + func TestConversion(t *testing.T) { strVar := "abc" stVar := st{s: "abc", i: 42} arrVal := [3]byte{1, 2, 3} + chanVal := make(chan string) tests := []conversionTest{ // $convertToInt64 @@ -372,6 +383,9 @@ func TestConversion(t *testing.T) { // $convertToMap mapConversion[map[string]string, m]{src: map[string]string{"abc": "def"}, want: m{"abc": "def"}}, mapConversion[map[string]string, m]{src: nil, want: nil}, + // $convertToChan + chanConversion[chan string, ch]{src: chanVal, want: ch(chanVal)}, + chanConversion[chan string, ch]{src: nil, want: nil}, } for _, test := range tests { From 1bc040ee2b7ab68e034354f23a27983dc640f135 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 21:13:40 +0000 Subject: [PATCH 07/10] Remove stray debug print. --- tests/js_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/js_test.go b/tests/js_test.go index 3e69849f1..6563098df 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -732,7 +732,6 @@ func TestReflection(t *testing.T) { s := S{o} v := reflect.ValueOf(&s).Elem() - println(v.Field(0).Interface()) if got := v.Field(0).Interface().(*js.Object).Get("answer").Int(); got != 42 { t.Errorf("Got: Accessing JS object property via reflect.Value.Interface() returned %v. Want: 42.", got) } From f2b91ba2fd48d6aa08ef5bd02d5a2ddb8d14239d Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 23:04:58 +0000 Subject: [PATCH 08/10] Implement type param to function types conversion support. --- compiler/prelude/types.js | 13 ++++++++++++- tests/gorepo/run.go | 1 - tests/typeparams/conversion_test.go | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/compiler/prelude/types.js b/compiler/prelude/types.js index df62c60ec..49baad391 100644 --- a/compiler/prelude/types.js +++ b/compiler/prelude/types.js @@ -478,6 +478,7 @@ var $newType = (size, kind, string, named, pkg, exported, constructor) => { typ.convertFrom = (src) => $convertToChan(src, typ); break; case $kindFunc: + typ.convertFrom = (src) => $convertToFunc(src, typ); break; default: $panic(new $String("invalid kind: " + kind)); @@ -1214,4 +1215,14 @@ const $convertToMap = (src, dstType) => { */ const $convertToChan = (src, dstType) => { return src.$val; -}; \ No newline at end of file +}; + +/** + * Convert to function types. + * + * dstType.kind must be $kindFunc. Src must be a wrapped function value. Returned + * value will always be a bare JavaScript function. + */ +const $convertToFunc = (src, dstType) => { + return src.$val; +}; diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index c118546fc..0e1cb959e 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -168,7 +168,6 @@ var knownFails = map[string]failReason{ "typeparam/index2.go": {category: generics, desc: "missing index operator support for generic types"}, "typeparam/issue47258.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/issue47716.go": {category: generics, desc: "unsafe.Sizeof() doesn't work with generic types"}, - "typeparam/issue48137.go": {category: generics, desc: "unsupported conversion from func() main.Bar to main.Bar"}, "typeparam/issue48276a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index 539957741..2d9bf02bf 100644 --- a/tests/typeparams/conversion_test.go +++ b/tests/typeparams/conversion_test.go @@ -58,6 +58,7 @@ type ( // Named types for use in conversion test cases. arrPtr *[3]byte m map[string]string ch chan string + fun func() int ) type numeric interface { @@ -277,11 +278,28 @@ func (tc chanConversion[srcType, dstType]) Run(t *testing.T) { checkConversion(t, tc.src, dstType(tc.src), tc.want) } +type funcConversion[srcType ~func() int, dstType ~func() int] struct { + src srcType + want dstType +} + +func (tc funcConversion[srcType, dstType]) Run(t *testing.T) { + got := dstType(tc.src) + if reflect.TypeOf(got) != reflect.TypeOf(tc.want) { + t.Errorf("Got: %v. Want: converted type is: %v.", reflect.TypeOf(got), reflect.TypeOf(tc.want)) + } + + if js.InternalObject(got) != js.InternalObject(tc.want) { + t.Errorf("Got: %v != %v. Want: after type conversion function object should remain the same.", got, tc.want) + } +} + func TestConversion(t *testing.T) { strVar := "abc" stVar := st{s: "abc", i: 42} arrVal := [3]byte{1, 2, 3} chanVal := make(chan string) + funcVal := func() int { return 42 } tests := []conversionTest{ // $convertToInt64 @@ -386,6 +404,9 @@ func TestConversion(t *testing.T) { // $convertToChan chanConversion[chan string, ch]{src: chanVal, want: ch(chanVal)}, chanConversion[chan string, ch]{src: nil, want: nil}, + // $convertToFunc + funcConversion[func() int, fun]{src: funcVal, want: fun(funcVal)}, + funcConversion[func() int, fun]{src: nil, want: nil}, } for _, test := range tests { From 4e6c8bcb00dcad6fb00fab06fc25dd034cf1afe8 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 23:22:02 +0000 Subject: [PATCH 09/10] Correctly recognize type conversion expressions with multiple typeparams. Prior to this change expressions like `ss[T1, T2](val)` where `ss` is a parameterized type were not recognized as type conversion expressions due to use of the *ast.IndexListExpr node. --- compiler/astutil/astutil.go | 7 +++++++ tests/gorepo/run.go | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index f97cdaa4f..e6a60c421 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -60,6 +60,13 @@ func IsTypeExpr(expr ast.Expr, info *types.Info) bool { } _, ok = info.Uses[ident].(*types.TypeName) return ok + case *ast.IndexListExpr: + ident, ok := e.X.(*ast.Ident) + if !ok { + return false + } + _, ok = info.Uses[ident].(*types.TypeName) + return ok case *ast.ParenExpr: return IsTypeExpr(e.X, info) default: diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 0e1cb959e..620b7907d 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -172,7 +172,7 @@ var knownFails = map[string]failReason{ "typeparam/issue48453.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/issue49295.go": {category: generics, desc: "len() doesn't support generic pointer to array types"}, "typeparam/issue50193.go": {category: generics, desc: "invalid print format for complex numbers"}, - "typeparam/issue51303.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/issue51303.go": {category: generics, desc: "missing support for range over type parameter"}, "typeparam/issue51522a.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/issue51522b.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/list.go": {category: generics, desc: "missing operator support for generic types"}, From 267a750d1666229b32fb67967de2d735170c423d Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Wed, 1 Nov 2023 23:46:46 +0000 Subject: [PATCH 10/10] Re-triage gorepo test failures associated with type conversion. At this point both remaining issues seem to be related to other kinds of issues. --- tests/gorepo/run.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 620b7907d..fabcd655a 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -157,9 +157,9 @@ var knownFails = map[string]failReason{ "typeparam/absdiff.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/absdiff2.go": {category: generics, desc: "missing operator support for generic types"}, "typeparam/absdiff3.go": {category: generics, desc: "missing operator support for generic types"}, - "typeparam/boundmethod.go": {category: generics, desc: "missing support for type conversion of a parameterized type"}, + "typeparam/boundmethod.go": {category: generics, desc: "missing support for method expressions with a type param"}, "typeparam/chans.go": {category: generics, desc: "missing support for the comparable type constraint"}, - "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for conversion into a parameterized type"}, + "typeparam/dictionaryCapture.go": {category: generics, desc: "missing support for basic literals with type params"}, "typeparam/double.go": {category: generics, desc: "make() doesn't support generic slice types"}, "typeparam/equal.go": {category: generics, desc: "missing support for the comparable type constraint"}, "typeparam/fact.go": {category: generics, desc: "missing support for the comparable type constraint"},