diff --git a/compiler/interface.go b/compiler/interface.go index c8b76039..e98285a2 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 b53297b7..0afbee03 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 0f66a30e..d13399f3 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 2c442696..04e3fb9e 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 c4ecae77..188b0a3c 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