From 047ed57005b63d80a21abe36d74e939a8291b009 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:52:06 -0700 Subject: [PATCH] compiler: canonicalize method signature identities The interface lowering pass used a separate formatter for method signatures. Same-named local aliases could therefore make distinct generic methods compare equal. Reuse getTypeCodeName so interface checks preserve type identity. --- compiler/interface.go | 116 +----------------- compiler/testdata/generics.ll | 2 +- .../testdata/goroutine-cortex-m-qemu-tasks.ll | 2 +- compiler/testdata/goroutine-wasm-asyncify.ll | 2 +- compiler/testdata/interface.ll | 8 +- testdata/localtypes.txt | 4 +- 6 files changed, 15 insertions(+), 119 deletions(-) diff --git a/compiler/interface.go b/compiler/interface.go index 5c7460a31..3da7cbe26 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -980,14 +980,15 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { // getMethodSignatureName returns a unique name (that can be used as the name of // a global) for the given method. func (c *compilerContext) getMethodSignatureName(method *types.Func) string { - signature := methodSignature(method) - var globalName string + name := method.Name() + var prefix string if token.IsExported(method.Name()) { - globalName = "reflect/methods." + signature + prefix = "reflect/methods." } else { - globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature + prefix = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." } - return globalName + signature, _ := c.getTypeCodeName(method.Type()) + return prefix + name + ":" + signature } // getMethodSignature returns a global variable which is a reference to an @@ -1298,108 +1299,3 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType return wrapper } - -// methodSignature creates a readable version of a method signature (including -// the function name, excluding the receiver name). This string is used -// internally to match interfaces and to call the correct method on an -// interface. Examples: -// -// String() string -// Read([]byte) (int, error) -func methodSignature(method *types.Func) string { - return method.Name() + signature(method.Type().(*types.Signature)) -} - -// Make a readable version of a function (pointer) signature. -// Examples: -// -// () string -// (string, int) (int, error) -func signature(sig *types.Signature) string { - var s strings.Builder - if sig.Params().Len() == 0 { - s.WriteString("()") - } else { - s.WriteString("(") - i := 0 - for v := range sig.Params().Variables() { - if i > 0 { - s.WriteString(", ") - } - s.WriteString(typestring(v.Type())) - i++ - } - s.WriteString(")") - } - if sig.Results().Len() == 0 { - // keep as-is - } else if sig.Results().Len() == 1 { - s.WriteString(" " + typestring(sig.Results().At(0).Type())) - } else { - s.WriteString(" (") - i := 0 - for v := range sig.Results().Variables() { - if i > 0 { - s.WriteString(", ") - } - s.WriteString(typestring(v.Type())) - i++ - } - s.WriteString(")") - } - return s.String() -} - -// 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 := types.Unalias(t).(type) { - case *types.Array: - return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem()) - case *types.Basic: - return basicTypeNames[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/generics.ll b/compiler/testdata/generics.ll index 4896f9348..5b9f4ee98 100644 --- a/compiler/testdata/generics.ll +++ b/compiler/testdata/generics.ll @@ -192,5 +192,5 @@ declare i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[basic:float32]} attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #2 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } -attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Get() main.aliasMethodResult[main.F]" } +attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Get:func:{}{named:main.aliasMethodResult[basic:float32]}" } attributes #4 = { nounwind } diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index a1e81a9b1..56a609214 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -195,6 +195,6 @@ attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" } attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } -attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } +attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic:string}{}" } attributes #10 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #11 = { nounwind } diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index 137b601f7..72275bfc8 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -206,6 +206,6 @@ attributes #5 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+cal attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" } attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } -attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } +attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic:string}{}" } attributes #10 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #11 = { nounwind } diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index f1ba5858e..016a461a4 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -131,8 +131,8 @@ declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke" attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } -attributes #2 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error() string" } -attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String() string" } -attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" } -attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" } +attributes #2 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error:func:{}{basic:string}" } +attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String:func:{}{basic:string}" } +attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo:func:{basic:int}{basic:uint8}" "tinygo-methods"="reflect/methods.String:func:{}{basic:string}; main.$methods.foo:func:{basic:int}{basic:uint8}" } +attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error:func:{}{basic:string}" "tinygo-methods"="reflect/methods.Error:func:{}{basic:string}" } attributes #6 = { nounwind } diff --git a/testdata/localtypes.txt b/testdata/localtypes.txt index b0910553d..8ecff7d74 100644 --- a/testdata/localtypes.txt +++ b/testdata/localtypes.txt @@ -57,5 +57,5 @@ ok: aliasBox[float32] implements Get() float32 ok: aliasBox[float64] implements Get() float64 ok: aliasMethod[float32] accepts own ok: aliasMethod[float64] accepts own -BUG: aliasMethod[float32] rejects [float64] -BUG: aliasMethod[float64] rejects [float32] +ok: aliasMethod[float32] rejects [float64] +ok: aliasMethod[float64] rejects [float32]