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).
Этот коммит содержится в:
родитель
7af24c7864
коммит
c0d257d682
5 изменённых файлов: 110 добавлений и 64 удалений
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
2
compiler/testdata/interface.ll
предоставленный
2
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" }
|
||||
|
|
11
testdata/interface.go
предоставленный
11
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
|
||||
}
|
||||
|
|
1
testdata/interface.txt
предоставленный
1
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
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче