Blob Blame History Raw
From 0d544bf99353a1b1e2592d40094f6ace05d98e6c Mon Sep 17 00:00:00 2001
From: Ayke van Laethem <aykevanlaethem@gmail.com>
Date: Wed, 6 Apr 2022 21:22:04 +0200
Subject: [PATCH] compiler: fix difference in aliases in interface methods

There used to be a difference between `byte` and `uint8` in interface
methods. These are aliases, so they should be treated the same.
This patch introduces a custom serialization format for types,
circumventing the `Type.String()` method that is slightly wrong for our
purposes.

This also fixes an issue with the `any` keyword in Go 1.18, which
suffers from the same problem (but this time actually leads to a crash).
---
 compiler/interface.go          | 124 +++++++++++++++++++++------------
 compiler/testdata/interface.ll |   2 +-
 testdata/interface.go          |  11 +++
 testdata/interface.txt         |   1 +
 transform/reflect.go           |  36 +++++-----
 5 files changed, 110 insertions(+), 64 deletions(-)

diff --git a/compiler/interface.go b/compiler/interface.go
index c8b76039dc..e98285a2a1 100644
--- a/compiler/interface.go
+++ b/compiler/interface.go
@@ -152,6 +152,27 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
 	return structGlobal
 }
 
+var basicTypes = [...]string{
+	types.Bool:          "bool",
+	types.Int:           "int",
+	types.Int8:          "int8",
+	types.Int16:         "int16",
+	types.Int32:         "int32",
+	types.Int64:         "int64",
+	types.Uint:          "uint",
+	types.Uint8:         "uint8",
+	types.Uint16:        "uint16",
+	types.Uint32:        "uint32",
+	types.Uint64:        "uint64",
+	types.Uintptr:       "uintptr",
+	types.Float32:       "float32",
+	types.Float64:       "float64",
+	types.Complex64:     "complex64",
+	types.Complex128:    "complex128",
+	types.String:        "string",
+	types.UnsafePointer: "unsafe.Pointer",
+}
+
 // getTypeCodeName returns a name for this type that can be used in the
 // interface lowering pass to assign type codes as expected by the reflect
 // package. See getTypeCodeNum.
@@ -162,48 +183,7 @@ func getTypeCodeName(t types.Type) string {
 	case *types.Array:
 		return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
 	case *types.Basic:
-		var kind string
-		switch t.Kind() {
-		case types.Bool:
-			kind = "bool"
-		case types.Int:
-			kind = "int"
-		case types.Int8:
-			kind = "int8"
-		case types.Int16:
-			kind = "int16"
-		case types.Int32:
-			kind = "int32"
-		case types.Int64:
-			kind = "int64"
-		case types.Uint:
-			kind = "uint"
-		case types.Uint8:
-			kind = "uint8"
-		case types.Uint16:
-			kind = "uint16"
-		case types.Uint32:
-			kind = "uint32"
-		case types.Uint64:
-			kind = "uint64"
-		case types.Uintptr:
-			kind = "uintptr"
-		case types.Float32:
-			kind = "float32"
-		case types.Float64:
-			kind = "float64"
-		case types.Complex64:
-			kind = "complex64"
-		case types.Complex128:
-			kind = "complex128"
-		case types.String:
-			kind = "string"
-		case types.UnsafePointer:
-			kind = "unsafeptr"
-		default:
-			panic("unknown basic type: " + t.Name())
-		}
-		return "basic:" + kind
+		return "basic:" + basicTypes[t.Kind()]
 	case *types.Chan:
 		return "chan:" + getTypeCodeName(t.Elem())
 	case *types.Interface:
@@ -591,23 +571,77 @@ func signature(sig *types.Signature) string {
 			if i > 0 {
 				s += ", "
 			}
-			s += sig.Params().At(i).Type().String()
+			s += typestring(sig.Params().At(i).Type())
 		}
 		s += ")"
 	}
 	if sig.Results().Len() == 0 {
 		// keep as-is
 	} else if sig.Results().Len() == 1 {
-		s += " " + sig.Results().At(0).Type().String()
+		s += " " + typestring(sig.Results().At(0).Type())
 	} else {
 		s += " ("
 		for i := 0; i < sig.Results().Len(); i++ {
 			if i > 0 {
 				s += ", "
 			}
-			s += sig.Results().At(i).Type().String()
+			s += typestring(sig.Results().At(i).Type())
 		}
 		s += ")"
 	}
 	return s
 }
+
+// typestring returns a stable (human-readable) type string for the given type
+// that can be used for interface equality checks. It is almost (but not
+// exactly) the same as calling t.String(). The main difference is some
+// normalization around `byte` vs `uint8` for example.
+func typestring(t types.Type) string {
+	// See: https://github.com/golang/go/blob/master/src/go/types/typestring.go
+	switch t := t.(type) {
+	case *types.Array:
+		return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
+	case *types.Basic:
+		return basicTypes[t.Kind()]
+	case *types.Chan:
+		switch t.Dir() {
+		case types.SendRecv:
+			return "chan (" + typestring(t.Elem()) + ")"
+		case types.SendOnly:
+			return "chan<- (" + typestring(t.Elem()) + ")"
+		case types.RecvOnly:
+			return "<-chan (" + typestring(t.Elem()) + ")"
+		default:
+			panic("unknown channel direction")
+		}
+	case *types.Interface:
+		methods := make([]string, t.NumMethods())
+		for i := range methods {
+			method := t.Method(i)
+			methods[i] = method.Name() + signature(method.Type().(*types.Signature))
+		}
+		return "interface{" + strings.Join(methods, ";") + "}"
+	case *types.Map:
+		return "map[" + typestring(t.Key()) + "]" + typestring(t.Elem())
+	case *types.Named:
+		return t.String()
+	case *types.Pointer:
+		return "*" + typestring(t.Elem())
+	case *types.Signature:
+		return "func" + signature(t)
+	case *types.Slice:
+		return "[]" + typestring(t.Elem())
+	case *types.Struct:
+		fields := make([]string, t.NumFields())
+		for i := range fields {
+			field := t.Field(i)
+			fields[i] = field.Name() + " " + typestring(field.Type())
+			if tag := t.Tag(i); tag != "" {
+				fields[i] += " " + strconv.Quote(tag)
+			}
+		}
+		return "struct{" + strings.Join(fields, ";") + "}"
+	default:
+		panic("unknown type: " + t.String())
+	}
+}
diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll
index b53297b7f8..0afbee0300 100644
--- a/compiler/testdata/interface.ll
+++ b/compiler/testdata/interface.ll
@@ -128,5 +128,5 @@ declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"
 attributes #0 = { nounwind }
 attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
 attributes #2 = { "tinygo-methods"="reflect/methods.String() string" }
-attributes #3 = { "tinygo-invoke"="main.$methods.foo(int) byte" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) byte" }
+attributes #3 = { "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" }
 attributes #4 = { "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" }
diff --git a/testdata/interface.go b/testdata/interface.go
index 0f66a30e5d..d13399f36b 100644
--- a/testdata/interface.go
+++ b/testdata/interface.go
@@ -40,6 +40,9 @@ func main() {
 	// https://github.com/tinygo-org/tinygo/issues/453
 	_, _ = itf.(Empty)
 
+	var v Byter = FooByte(3)
+	println("Byte(): ", v.Byte())
+
 	var n int
 	var f float32
 	var interfaceEqualTests = []struct {
@@ -266,3 +269,11 @@ type StaticBlocker interface {
 }
 
 type Empty interface{}
+
+type FooByte int
+
+func (f FooByte) Byte() byte { return byte(f) }
+
+type Byter interface {
+	Byte() uint8
+}
diff --git a/testdata/interface.txt b/testdata/interface.txt
index 2c44269638..04e3fb9eac 100644
--- a/testdata/interface.txt
+++ b/testdata/interface.txt
@@ -20,6 +20,7 @@ Stringer.String(): foo
 Stringer.(*Thing).String(): foo
 s has String() method: foo
 nested switch: true
+Byte():  3
 non-blocking call on sometimes-blocking interface
 slept 1ms
 slept 1ms
diff --git a/transform/reflect.go b/transform/reflect.go
index c4ecae777f..188b0a3c18 100644
--- a/transform/reflect.go
+++ b/transform/reflect.go
@@ -40,24 +40,24 @@ import (
 // A list of basic types and their numbers. This list should be kept in sync
 // with the list of Kind constants of type.go in the reflect package.
 var basicTypes = map[string]int64{
-	"bool":       1,
-	"int":        2,
-	"int8":       3,
-	"int16":      4,
-	"int32":      5,
-	"int64":      6,
-	"uint":       7,
-	"uint8":      8,
-	"uint16":     9,
-	"uint32":     10,
-	"uint64":     11,
-	"uintptr":    12,
-	"float32":    13,
-	"float64":    14,
-	"complex64":  15,
-	"complex128": 16,
-	"string":     17,
-	"unsafeptr":  18,
+	"bool":           1,
+	"int":            2,
+	"int8":           3,
+	"int16":          4,
+	"int32":          5,
+	"int64":          6,
+	"uint":           7,
+	"uint8":          8,
+	"uint16":         9,
+	"uint32":         10,
+	"uint64":         11,
+	"uintptr":        12,
+	"float32":        13,
+	"float64":        14,
+	"complex64":      15,
+	"complex128":     16,
+	"string":         17,
+	"unsafe.Pointer": 18,
 }
 
 // A list of non-basic types. Adding 19 to this number will give the Kind as