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