Skip to content

Commit c0d257d

Browse files
aykevldeadprogram
authored andcommitted
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).
1 parent 7af24c7 commit c0d257d

File tree

5 files changed

+110
-64
lines changed

5 files changed

+110
-64
lines changed

compiler/interface.go

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,27 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
152152
return structGlobal
153153
}
154154

155+
var basicTypes = [...]string{
156+
types.Bool: "bool",
157+
types.Int: "int",
158+
types.Int8: "int8",
159+
types.Int16: "int16",
160+
types.Int32: "int32",
161+
types.Int64: "int64",
162+
types.Uint: "uint",
163+
types.Uint8: "uint8",
164+
types.Uint16: "uint16",
165+
types.Uint32: "uint32",
166+
types.Uint64: "uint64",
167+
types.Uintptr: "uintptr",
168+
types.Float32: "float32",
169+
types.Float64: "float64",
170+
types.Complex64: "complex64",
171+
types.Complex128: "complex128",
172+
types.String: "string",
173+
types.UnsafePointer: "unsafe.Pointer",
174+
}
175+
155176
// getTypeCodeName returns a name for this type that can be used in the
156177
// interface lowering pass to assign type codes as expected by the reflect
157178
// package. See getTypeCodeNum.
@@ -162,48 +183,7 @@ func getTypeCodeName(t types.Type) string {
162183
case *types.Array:
163184
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
164185
case *types.Basic:
165-
var kind string
166-
switch t.Kind() {
167-
case types.Bool:
168-
kind = "bool"
169-
case types.Int:
170-
kind = "int"
171-
case types.Int8:
172-
kind = "int8"
173-
case types.Int16:
174-
kind = "int16"
175-
case types.Int32:
176-
kind = "int32"
177-
case types.Int64:
178-
kind = "int64"
179-
case types.Uint:
180-
kind = "uint"
181-
case types.Uint8:
182-
kind = "uint8"
183-
case types.Uint16:
184-
kind = "uint16"
185-
case types.Uint32:
186-
kind = "uint32"
187-
case types.Uint64:
188-
kind = "uint64"
189-
case types.Uintptr:
190-
kind = "uintptr"
191-
case types.Float32:
192-
kind = "float32"
193-
case types.Float64:
194-
kind = "float64"
195-
case types.Complex64:
196-
kind = "complex64"
197-
case types.Complex128:
198-
kind = "complex128"
199-
case types.String:
200-
kind = "string"
201-
case types.UnsafePointer:
202-
kind = "unsafeptr"
203-
default:
204-
panic("unknown basic type: " + t.Name())
205-
}
206-
return "basic:" + kind
186+
return "basic:" + basicTypes[t.Kind()]
207187
case *types.Chan:
208188
return "chan:" + getTypeCodeName(t.Elem())
209189
case *types.Interface:
@@ -591,23 +571,77 @@ func signature(sig *types.Signature) string {
591571
if i > 0 {
592572
s += ", "
593573
}
594-
s += sig.Params().At(i).Type().String()
574+
s += typestring(sig.Params().At(i).Type())
595575
}
596576
s += ")"
597577
}
598578
if sig.Results().Len() == 0 {
599579
// keep as-is
600580
} else if sig.Results().Len() == 1 {
601-
s += " " + sig.Results().At(0).Type().String()
581+
s += " " + typestring(sig.Results().At(0).Type())
602582
} else {
603583
s += " ("
604584
for i := 0; i < sig.Results().Len(); i++ {
605585
if i > 0 {
606586
s += ", "
607587
}
608-
s += sig.Results().At(i).Type().String()
588+
s += typestring(sig.Results().At(i).Type())
609589
}
610590
s += ")"
611591
}
612592
return s
613593
}
594+
595+
// typestring returns a stable (human-readable) type string for the given type
596+
// that can be used for interface equality checks. It is almost (but not
597+
// exactly) the same as calling t.String(). The main difference is some
598+
// normalization around `byte` vs `uint8` for example.
599+
func typestring(t types.Type) string {
600+
// See: https://github.com/golang/go/blob/master/src/go/types/typestring.go
601+
switch t := t.(type) {
602+
case *types.Array:
603+
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
604+
case *types.Basic:
605+
return basicTypes[t.Kind()]
606+
case *types.Chan:
607+
switch t.Dir() {
608+
case types.SendRecv:
609+
return "chan (" + typestring(t.Elem()) + ")"
610+
case types.SendOnly:
611+
return "chan<- (" + typestring(t.Elem()) + ")"
612+
case types.RecvOnly:
613+
return "<-chan (" + typestring(t.Elem()) + ")"
614+
default:
615+
panic("unknown channel direction")
616+
}
617+
case *types.Interface:
618+
methods := make([]string, t.NumMethods())
619+
for i := range methods {
620+
method := t.Method(i)
621+
methods[i] = method.Name() + signature(method.Type().(*types.Signature))
622+
}
623+
return "interface{" + strings.Join(methods, ";") + "}"
624+
case *types.Map:
625+
return "map[" + typestring(t.Key()) + "]" + typestring(t.Elem())
626+
case *types.Named:
627+
return t.String()
628+
case *types.Pointer:
629+
return "*" + typestring(t.Elem())
630+
case *types.Signature:
631+
return "func" + signature(t)
632+
case *types.Slice:
633+
return "[]" + typestring(t.Elem())
634+
case *types.Struct:
635+
fields := make([]string, t.NumFields())
636+
for i := range fields {
637+
field := t.Field(i)
638+
fields[i] = field.Name() + " " + typestring(field.Type())
639+
if tag := t.Tag(i); tag != "" {
640+
fields[i] += " " + strconv.Quote(tag)
641+
}
642+
}
643+
return "struct{" + strings.Join(fields, ";") + "}"
644+
default:
645+
panic("unknown type: " + t.String())
646+
}
647+
}

compiler/testdata/interface.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,5 +128,5 @@ declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"
128128
attributes #0 = { nounwind }
129129
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
130130
attributes #2 = { "tinygo-methods"="reflect/methods.String() string" }
131-
attributes #3 = { "tinygo-invoke"="main.$methods.foo(int) byte" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) byte" }
131+
attributes #3 = { "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" }
132132
attributes #4 = { "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" }

testdata/interface.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ func main() {
4040
// https://github.com/tinygo-org/tinygo/issues/453
4141
_, _ = itf.(Empty)
4242

43+
var v Byter = FooByte(3)
44+
println("Byte(): ", v.Byte())
45+
4346
var n int
4447
var f float32
4548
var interfaceEqualTests = []struct {
@@ -266,3 +269,11 @@ type StaticBlocker interface {
266269
}
267270

268271
type Empty interface{}
272+
273+
type FooByte int
274+
275+
func (f FooByte) Byte() byte { return byte(f) }
276+
277+
type Byter interface {
278+
Byte() uint8
279+
}

testdata/interface.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Stringer.String(): foo
2020
Stringer.(*Thing).String(): foo
2121
s has String() method: foo
2222
nested switch: true
23+
Byte(): 3
2324
non-blocking call on sometimes-blocking interface
2425
slept 1ms
2526
slept 1ms

transform/reflect.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,24 @@ import (
4040
// A list of basic types and their numbers. This list should be kept in sync
4141
// with the list of Kind constants of type.go in the reflect package.
4242
var basicTypes = map[string]int64{
43-
"bool": 1,
44-
"int": 2,
45-
"int8": 3,
46-
"int16": 4,
47-
"int32": 5,
48-
"int64": 6,
49-
"uint": 7,
50-
"uint8": 8,
51-
"uint16": 9,
52-
"uint32": 10,
53-
"uint64": 11,
54-
"uintptr": 12,
55-
"float32": 13,
56-
"float64": 14,
57-
"complex64": 15,
58-
"complex128": 16,
59-
"string": 17,
60-
"unsafeptr": 18,
43+
"bool": 1,
44+
"int": 2,
45+
"int8": 3,
46+
"int16": 4,
47+
"int32": 5,
48+
"int64": 6,
49+
"uint": 7,
50+
"uint8": 8,
51+
"uint16": 9,
52+
"uint32": 10,
53+
"uint64": 11,
54+
"uintptr": 12,
55+
"float32": 13,
56+
"float64": 14,
57+
"complex64": 15,
58+
"complex128": 16,
59+
"string": 17,
60+
"unsafe.Pointer": 18,
6161
}
6262

6363
// A list of non-basic types. Adding 19 to this number will give the Kind as

0 commit comments

Comments
 (0)