mirror of
https://github.com/goplus/llgo.git
synced 2026-04-22 15:57:31 +08:00
cl: tighten funcName wrapper fallback and add regressions
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/goplus/gogen/packages"
|
||||
@@ -16,6 +17,63 @@ import (
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
)
|
||||
|
||||
func buildSSAPackage(t *testing.T, src string) *ssa.Package {
|
||||
t.Helper()
|
||||
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "foo.go", src, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
files := []*ast.File{f}
|
||||
pkg := types.NewPackage("foo", "foo")
|
||||
imp := packages.NewImporter(fset)
|
||||
mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics
|
||||
ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: imp}, fset, pkg, files, mode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return ssapkg
|
||||
}
|
||||
|
||||
func collectFuncNamesBySSAName(ssapkg *ssa.Package, ssaName string) []string {
|
||||
var got []string
|
||||
for fn := range ssautil.AllFunctions(ssapkg.Prog) {
|
||||
if fn == nil || fn.Name() != ssaName {
|
||||
continue
|
||||
}
|
||||
got = append(got, funcName(ssapkg.Pkg, fn, false))
|
||||
}
|
||||
sort.Strings(got)
|
||||
return got
|
||||
}
|
||||
|
||||
func expectTwoDistinctNamesForTypes(t *testing.T, got []string, suffix string) {
|
||||
t.Helper()
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("got %d function names, want 2: %v", len(got), got)
|
||||
}
|
||||
if got[0] == got[1] {
|
||||
t.Fatalf("got duplicate names: %v", got)
|
||||
}
|
||||
|
||||
var hasA, hasB bool
|
||||
for _, name := range got {
|
||||
if !strings.Contains(name, suffix) {
|
||||
t.Fatalf("name %q missing %q", name, suffix)
|
||||
}
|
||||
if strings.Contains(name, "A") {
|
||||
hasA = true
|
||||
}
|
||||
if strings.Contains(name, "B") {
|
||||
hasB = true
|
||||
}
|
||||
}
|
||||
if !hasA || !hasB {
|
||||
t.Fatalf("expected names for both A and B, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncName_NestedClosureInMethodIncludesRecv(t *testing.T) {
|
||||
const src = `package foo
|
||||
|
||||
@@ -39,28 +97,9 @@ func (b *B) marshal() int {
|
||||
}
|
||||
`
|
||||
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "foo.go", src, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
files := []*ast.File{f}
|
||||
pkg := types.NewPackage("foo", "foo")
|
||||
imp := packages.NewImporter(fset)
|
||||
mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics
|
||||
ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: imp}, fset, pkg, files, mode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ssapkg := buildSSAPackage(t, src)
|
||||
|
||||
var got []string
|
||||
for fn := range ssautil.AllFunctions(ssapkg.Prog) {
|
||||
if fn == nil || fn.Name() != "marshal$1$1" {
|
||||
continue
|
||||
}
|
||||
got = append(got, funcName(ssapkg.Pkg, fn, false))
|
||||
}
|
||||
sort.Strings(got)
|
||||
got := collectFuncNamesBySSAName(ssapkg, "marshal$1$1")
|
||||
|
||||
want := []string{
|
||||
"foo.(*A).marshal$1$1",
|
||||
@@ -77,3 +116,55 @@ func (b *B) marshal() int {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncName_TopLevelAndPlainClosureNoRecv(t *testing.T) {
|
||||
const src = `package foo
|
||||
|
||||
func top() {}
|
||||
|
||||
func outer() {
|
||||
f := func() {}
|
||||
f()
|
||||
}
|
||||
`
|
||||
ssapkg := buildSSAPackage(t, src)
|
||||
|
||||
topFn := ssapkg.Func("top")
|
||||
if topFn == nil {
|
||||
t.Fatal("top function not found")
|
||||
}
|
||||
if got, want := funcName(ssapkg.Pkg, topFn, false), "foo.top"; got != want {
|
||||
t.Fatalf("top-level func name = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
closures := collectFuncNamesBySSAName(ssapkg, "outer$1")
|
||||
if len(closures) != 1 {
|
||||
t.Fatalf("got %d closure name(s) for outer$1: %v, want 1", len(closures), closures)
|
||||
}
|
||||
if got, want := closures[0], "foo.outer$1"; got != want {
|
||||
t.Fatalf("closure name = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncName_ThunkAndBoundStillCarryRecv(t *testing.T) {
|
||||
const src = `package foo
|
||||
|
||||
type A int
|
||||
type B int
|
||||
|
||||
func (A) f() int { return 1 }
|
||||
func (B) f() int { return 2 }
|
||||
|
||||
var ta = A.f
|
||||
var tb = B.f
|
||||
var ba = A(0).f
|
||||
var bb = B(0).f
|
||||
`
|
||||
ssapkg := buildSSAPackage(t, src)
|
||||
|
||||
thunks := collectFuncNamesBySSAName(ssapkg, "f$thunk")
|
||||
expectTwoDistinctNamesForTypes(t, thunks, "f$thunk")
|
||||
|
||||
bounds := collectFuncNamesBySSAName(ssapkg, "f$bound")
|
||||
expectTwoDistinctNamesForTypes(t, bounds, "f$bound")
|
||||
}
|
||||
|
||||
+11
-13
@@ -415,26 +415,24 @@ func funcName(pkg *types.Package, fn *ssa.Function, org bool) string {
|
||||
// Closures in methods can be nested (closure inside closure inside method).
|
||||
// Walking only one Parent() loses the receiver for deeper nests, producing
|
||||
// names like "pkg.marshal$1$1" that can collide across receiver types.
|
||||
// Walk parents until we find a receiver or a thunk/bound pattern.
|
||||
// Walk parents until we find a receiver.
|
||||
var recv *types.Var
|
||||
for f := fn; f != nil; f = f.Parent() {
|
||||
recv = f.Signature.Recv()
|
||||
if recv != nil {
|
||||
break
|
||||
}
|
||||
name := f.Name()
|
||||
// check $thunk and $bound
|
||||
if strings.HasSuffix(name, "$thunk") {
|
||||
// For thunks, extract receiver from first parameter.
|
||||
if params := f.Signature.Params(); params.Len() > 0 {
|
||||
recv = params.At(0)
|
||||
break
|
||||
}
|
||||
} else if strings.HasSuffix(name, "$bound") && len(f.FreeVars) == 1 {
|
||||
// For bound method wrappers, synthesize receiver var from free var type.
|
||||
recv = types.NewVar(token.NoPos, nil, "", f.FreeVars[0].Type())
|
||||
break
|
||||
}
|
||||
// For wrappers, fall back to metadata available on fn itself.
|
||||
name := fn.Name()
|
||||
if recv == nil && strings.HasSuffix(name, "$thunk") {
|
||||
// For thunks, extract receiver from first parameter.
|
||||
if params := fn.Signature.Params(); params.Len() > 0 {
|
||||
recv = params.At(0)
|
||||
}
|
||||
} else if recv == nil && strings.HasSuffix(name, "$bound") && len(fn.FreeVars) == 1 {
|
||||
// For bound method wrappers, synthesize receiver var from free var type.
|
||||
recv = types.NewVar(token.NoPos, nil, "", fn.FreeVars[0].Type())
|
||||
}
|
||||
var fnName string
|
||||
if org := fn.Origin(); org != nil {
|
||||
|
||||
Reference in New Issue
Block a user