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/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/compiler/prelude/types.js b/compiler/prelude/types.js index 362921a61..49baad391 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; @@ -456,13 +459,26 @@ 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 $kindPtr: + 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: + typ.convertFrom = (src) => $convertToMap(src, typ); + break; case $kindChan: - case $kindPtr: + typ.convertFrom = (src) => $convertToChan(src, typ); + break; case $kindFunc: - case $kindStruct: + typ.convertFrom = (src) => $convertToFunc(src, typ); break; default: $panic(new $String("invalid kind: " + kind)); @@ -1093,4 +1109,120 @@ const $convertToBool = (src, dstType) => { */ const $convertToInterface = (src, dstType) => { return src; -}; \ No newline at end of file +}; + +/** + * 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. + */ +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}`); +}; + +/** +* 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); + } +}; + +/** + * 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); +}; + +/** + * 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; +}; + +/** + * 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; +}; + +/** + * 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 cd4ab344c..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"}, @@ -168,13 +168,11 @@ 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"}, "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/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"}, 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) } 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.") + } + }) +} diff --git a/tests/typeparams/conversion_test.go b/tests/typeparams/conversion_test.go index b59d5cd6c..2d9bf02bf 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" @@ -36,6 +37,30 @@ 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 + m map[string]string + ch chan string + fun func() int +) + type numeric interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | @@ -159,20 +184,123 @@ func (tc interfaceConversion[srcType]) Run(t *testing.T) { } } -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 b bool - type st struct { - s string - i int +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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 numericConversion[int, int64]{src: 0x7FFFFFFF, want: 0x7FFFFFFF}, @@ -243,6 +371,42 @@ 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'}}, + // $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}, + // $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}}, + // $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}, + // $convertToFunc + funcConversion[func() int, fun]{src: funcVal, want: fun(funcVal)}, + funcConversion[func() int, fun]{src: nil, want: nil}, } for _, test := range tests {