Skip to content

Complete type conversion support for type parameters #1242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 2, 2023
Merged
7 changes: 7 additions & 0 deletions compiler/astutil/astutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion compiler/natives/src/reflect/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand Down
144 changes: 138 additions & 6 deletions compiler/prelude/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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; };
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -1093,4 +1109,120 @@ const $convertToBool = (src, dstType) => {
*/
const $convertToInterface = (src, dstType) => {
return src;
};
};

/**
* 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;
};
8 changes: 3 additions & 5 deletions tests/gorepo/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand All @@ -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"},
Expand Down
1 change: 0 additions & 1 deletion tests/js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
28 changes: 28 additions & 0 deletions tests/misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}
})
}
Loading