diff --git a/build_test.go b/build_test.go index 4ba65c2e4d..628144751a 100644 --- a/build_test.go +++ b/build_test.go @@ -51,6 +51,7 @@ func TestBuild(t *testing.T) { "math.go", "oldgo/", "print.go", + "promoted.go", "reflect.go", "signal.go", "signalnotify.go", diff --git a/compiler/symbol.go b/compiler/symbol.go index d3a80b47de..f3f6799a33 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -365,14 +365,37 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { func (c *compilerContext) canonicalFunctionName(f *ssa.Function) string { typeArgs := f.TypeArgs() - if len(typeArgs) == 0 { - return f.RelString(nil) + name := f.RelString(nil) + if len(typeArgs) != 0 { + parts := make([]string, len(typeArgs)) + for i, ta := range typeArgs { + parts[i], _ = c.getTypeCodeName(ta) + } + name = f.Origin().RelString(nil) + "[" + strings.Join(parts, ",") + "]" + } + return name + methodPackageQualifier(f) +} + +// methodPackageQualifier adds the declaring package to an unexported method +// promoted from another package, since RelString names it like the type's own. +func methodPackageQualifier(f *ssa.Function) string { + recv := f.Signature.Recv() + if recv == nil { + return "" + } + obj, ok := f.Object().(*types.Func) + if !ok || obj == nil || obj.Exported() || obj.Pkg() == nil { + return "" + } + t := recv.Type() + if ptr, ok := t.(*types.Pointer); ok { + t = ptr.Elem() } - parts := make([]string, len(typeArgs)) - for i, ta := range typeArgs { - parts[i], _ = c.getTypeCodeName(ta) + named, ok := types.Unalias(t).(*types.Named) + if !ok || named.Obj().Pkg() == nil || named.Obj().Pkg().Path() == obj.Pkg().Path() { + return "" } - return f.Origin().RelString(nil) + "[" + strings.Join(parts, ",") + "]" + return "$" + obj.Pkg().Path() } // parsePragmas is used by getFunctionInfo to parse function pragmas such as diff --git a/testdata/promoted.go b/testdata/promoted.go new file mode 100644 index 0000000000..63680b6053 --- /dev/null +++ b/testdata/promoted.go @@ -0,0 +1,65 @@ +package main + +import ( + "github.com/tinygo-org/tinygo/testdata/promoted/inner" + "github.com/tinygo-org/tinygo/testdata/promoted/inner2" +) + +// Outer has its own unexported methods and also the ones promoted from +// inner.Inner, which Go treats as different methods. +type Outer struct { + inner.Inner +} + +func (Outer) value() int { return 2 } + +func (*Outer) ptr() int { return 2 } + +type PtrOuter struct { + *inner.Inner +} + +func (PtrOuter) value() int { return 2 } + +func (PtrOuter) ptr() int { return 2 } + +type Outer2 struct { + Outer +} + +type Both struct { + inner.Inner + inner2.Inner2 +} + +type G[T any] struct { + inner.Inner + v T +} + +func (G[T]) value() int { return 2 } + +type mainValuer interface{ value() int } + +func main() { + println("own:", Outer{}.value()) + println("promoted:", inner.Value(Outer{})) + println("own ptr:", (&Outer{}).ptr()) + println("promoted ptr:", inner.Ptr(&Outer{})) + + po := PtrOuter{&inner.Inner{}} + println("embedded ptr own:", po.value()) + println("embedded ptr promoted:", inner.Value(po)) + println("embedded ptr promoted ptr:", inner.Ptr(po)) + + println("two levels main:", any(Outer2{}).(mainValuer).value()) + println("two levels inner:", inner.Value(Outer2{})) + println("two levels inner ptr:", inner.Ptr(&Outer2{})) + + println("both inner:", inner.Value(Both{})) + println("both inner2:", inner2.Value(Both{})) + + println("generic own:", G[int]{}.value()) + println("generic promoted:", inner.Value(G[int]{})) + println("generic string promoted:", inner.Value(G[string]{})) +} diff --git a/testdata/promoted.txt b/testdata/promoted.txt new file mode 100644 index 0000000000..5c368cc3eb --- /dev/null +++ b/testdata/promoted.txt @@ -0,0 +1,15 @@ +own: 2 +promoted: 1 +own ptr: 2 +promoted ptr: 1 +embedded ptr own: 2 +embedded ptr promoted: 1 +embedded ptr promoted ptr: 1 +two levels main: 2 +two levels inner: 1 +two levels inner ptr: 1 +both inner: 1 +both inner2: 3 +generic own: 2 +generic promoted: 1 +generic string promoted: 1 diff --git a/testdata/promoted/inner/inner.go b/testdata/promoted/inner/inner.go new file mode 100644 index 0000000000..5ff5be1f3e --- /dev/null +++ b/testdata/promoted/inner/inner.go @@ -0,0 +1,17 @@ +package inner + +type Inner struct{} + +func (Inner) value() int { return 1 } + +func (*Inner) ptr() int { return 1 } + +type valuer interface{ value() int } + +type ptrer interface{ ptr() int } + +// Value calls the value method of package inner through an interface. +func Value(v any) int { return v.(valuer).value() } + +// Ptr calls the pointer method of package inner through an interface. +func Ptr(v any) int { return v.(ptrer).ptr() } diff --git a/testdata/promoted/inner2/inner2.go b/testdata/promoted/inner2/inner2.go new file mode 100644 index 0000000000..b80af03638 --- /dev/null +++ b/testdata/promoted/inner2/inner2.go @@ -0,0 +1,9 @@ +package inner2 + +type Inner2 struct{} + +func (Inner2) value() int { return 3 } + +type valuer interface{ value() int } + +func Value(v any) int { return v.(valuer).value() }