diff --git a/cl/_testgo/tpycombinator/expect.txt b/cl/_testgo/tpycombinator/expect.txt new file mode 100644 index 0000000000..c116b4f11f --- /dev/null +++ b/cl/_testgo/tpycombinator/expect.txt @@ -0,0 +1,2 @@ +3628800 +xxx diff --git a/cl/_testgo/tpycombinator/in.go b/cl/_testgo/tpycombinator/in.go new file mode 100644 index 0000000000..b8180c0a66 --- /dev/null +++ b/cl/_testgo/tpycombinator/in.go @@ -0,0 +1,37 @@ +// LITTEST +package main + +// CHECK-DAG: %"{{.*}}tpycombinator.internal[{{.*}},int,int;{{.*}},int,int]" = type { ptr, ptr } +// CHECK-DAG: %"{{.*}}tpycombinator.internal[{{.*}},string,string;{{.*}},string,string]" = type { ptr, ptr } + +func Y[Endo ~func(RecFct) RecFct, RecFct ~func(T) R, T, R any](f Endo) RecFct { + type internal[RecFct ~func(T) R, T, R any] func(internal[RecFct, T, R]) RecFct + + g := func(h internal[RecFct, T, R]) RecFct { + return func(t T) R { + return f(h(h))(t) + } + } + return g(g) +} + +func main() { + factorial := Y(func(recur func(int) int) func(int) int { + return func(n int) int { + if n == 0 { + return 1 + } + return n * recur(n-1) + } + }) + repeat := Y(func(recur func(string) string) func(string) string { + return func(s string) string { + if len(s) == 3 { + return s + } + return recur(s + "x") + } + }) + println(factorial(10)) + println(repeat("")) +} diff --git a/cl/compile.go b/cl/compile.go index 8f99b9a0f5..9dfc1d6851 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -415,7 +415,13 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) { } } -func makeClosureCtx(pkg *types.Package, vars []*ssa.FreeVar) *types.Var { +func (p *context) makeClosureCtx(fn *ssa.Function, pkg *types.Package, vars []*ssa.FreeVar) *types.Var { + oldGoFn := p.goFn + p.goFn = fn + defer func() { + p.goFn = oldGoFn + }() + n := len(vars) flds := make([]*types.Var, n) for i, v := range vars { @@ -423,7 +429,7 @@ func makeClosureCtx(pkg *types.Package, vars []*ssa.FreeVar) *types.Var { if name == "" { name = "_" } - flds[i] = types.NewField(token.NoPos, pkg, name, v.Type(), false) + flds[i] = types.NewField(token.NoPos, pkg, name, p.patchType(v.Type()), false) } t := types.NewPointer(types.NewStruct(flds, nil)) return types.NewParam(token.NoPos, pkg, "__llgo_ctx", t) @@ -552,7 +558,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun var hasCtx = len(f.FreeVars) > 0 if hasCtx { dbgInstrln("==> NewClosure", name, "type:", sig) - ctx := makeClosureCtx(pkgTypes, f.FreeVars) + ctx := p.makeClosureCtx(f, pkgTypes, f.FreeVars) sig = llssa.FuncAddCtx(ctx, sig) } else { dbgInstrln("==> NewFunc", name, "type:", sig.Recv(), sig, "ftype:", ftype) @@ -2513,19 +2519,16 @@ func (p *context) localTypeOrdinal(obj types.Object) int { } func (p *context) inCurrentFunction(pos token.Pos) bool { - if !pos.IsValid() { - return false - } - syntax := p.currentFunctionSyntax() - return syntax != nil && syntax.Pos() <= pos && pos <= syntax.End() + return p.enclosingFunctionSyntax(pos) != nil } func (p *context) localTypeOrdinalBySyntax(pos token.Pos) int { - if !p.inCurrentFunction(pos) { + syntax := p.enclosingFunctionSyntax(pos) + if syntax == nil { return 0 } n := 0 - ast.Inspect(p.currentFunctionSyntax(), func(node ast.Node) bool { + ast.Inspect(syntax, func(node ast.Node) bool { spec, ok := node.(*ast.TypeSpec) if !ok { return true @@ -2538,15 +2541,22 @@ func (p *context) localTypeOrdinalBySyntax(pos token.Pos) int { return n } -func (p *context) currentFunctionSyntax() ast.Node { - if p.goFn == nil { +func (p *context) enclosingFunctionSyntax(pos token.Pos) ast.Node { + if !pos.IsValid() { return nil } - fn := p.goFn - if origin := fn.Origin(); origin != nil { - fn = origin + // Instantiated local types may lose their scope parent while a nested + // closure still refers to a declaration in its enclosing generic function. + for fn := p.goFn; fn != nil; fn = fn.Parent() { + syntaxFn := fn + if origin := fn.Origin(); origin != nil { + syntaxFn = origin + } + if syntax := syntaxFn.Syntax(); syntax != nil && syntax.Pos() <= pos && pos <= syntax.End() { + return syntax + } } - return fn.Syntax() + return nil } func isTypeParamObject(obj types.Object) bool { diff --git a/cl/funcname_nested_closure_test.go b/cl/funcname_nested_closure_test.go index 6f9034ea6c..e1df6b9a14 100644 --- a/cl/funcname_nested_closure_test.go +++ b/cl/funcname_nested_closure_test.go @@ -136,6 +136,102 @@ func localType[T any]() any { } } +func TestGenericLocalRecursiveClosureContextTypePatch(t *testing.T) { + ssapkg := buildSSAPackage(t, `package foo + +func Y[Endo ~func(RecFct) RecFct, RecFct ~func(T) R, T, R any](f Endo) RecFct { + type internal[RecFct ~func(T) R, T, R any] func(internal[RecFct, T, R]) RecFct + g := func(h internal[RecFct, T, R]) RecFct { + return func(t T) R { return f(h(h))(t) } + } + return g(g) +} + +func use() { + intFn := Y(func(recur func(int) int) func(int) int { return recur }) + stringFn := Y(func(recur func(string) string) func(string) string { return recur }) + _ = intFn(1) + _ = stringFn("") +} +`) + + y := ssapkg.Func("Y") + if y == nil { + t.Fatal("generic Y function not found") + } + names := make(map[types.BasicKind]string) + for instance := range ssautil.AllFunctions(ssapkg.Prog) { + if instance == nil || instance.Origin() != y { + continue + } + args := instance.TypeArgs() + if len(args) < 3 { + t.Fatalf("Y instance type arguments = %v, want T argument", args) + } + basic, ok := args[2].(*types.Basic) + if !ok || basic.Kind() != types.Int && basic.Kind() != types.String { + t.Fatalf("Y instance T = %v, want int or string", args[2]) + } + + if len(instance.AnonFuncs) == 0 { + t.Fatal("Y instance outer closure not found") + } + outer := instance.AnonFuncs[0] + if len(outer.AnonFuncs) == 0 { + t.Fatal("Y instance inner closure not found") + } + inner := outer.AnonFuncs[0] + + ctx := &context{goFn: outer} + patched := ctx.patchType(outer.Signature).(*types.Signature) + param := findLocalNamed(patched.Params().At(0).Type(), ssapkg.Pkg) + if param == nil { + t.Fatalf("patched Y outer closure parameter = %v, want local named function type", patched.Params().At(0).Type()) + } + + hIndex := -1 + for i, freeVar := range inner.FreeVars { + if freeVar.Name() == "h" { + hIndex = i + break + } + } + if hIndex < 0 { + t.Fatalf("Y inner closure free variables = %v, want h", inner.FreeVars) + } + closureCtx := ctx.makeClosureCtx(inner, ssapkg.Pkg, inner.FreeVars) + ptr, ok := closureCtx.Type().(*types.Pointer) + if !ok { + t.Fatalf("closure context type = %T, want pointer", closureCtx.Type()) + } + fields, ok := ptr.Elem().(*types.Struct) + if !ok { + t.Fatalf("closure context element = %T, want struct", ptr.Elem()) + } + hType := findLocalNamed(fields.Field(hIndex).Type(), ssapkg.Pkg) + if hType == nil { + t.Fatalf("closure context h field = %v, want local named function type", fields.Field(hIndex).Type()) + } + + name := param.Obj().Name() + if !strings.Contains(name, ";") { + t.Fatalf("patched outer parameter name = %q, want outer and own type arguments", name) + } + if hName := hType.Obj().Name(); hName != name { + t.Fatalf("patched h field name = %q, want outer parameter name %q", hName, name) + } + names[basic.Kind()] = name + } + intName, hasInt := names[types.Int] + stringName, hasString := names[types.String] + if !hasInt || !hasString { + t.Fatalf("patched local type names = %v, want int and string instances", names) + } + if intName == stringName { + t.Fatalf("int and string instances share patched local type name %q", intName) + } +} + func TestGenericLocalTypePatchHelperBranches(t *testing.T) { ssapkg := buildSSAPackage(t, `package foo @@ -306,9 +402,6 @@ func use() { if ctx.inCurrentFunction(token.NoPos) { t.Fatal("invalid position should not be inside current function") } - if (&context{}).currentFunctionSyntax() != nil { - t.Fatal("currentFunctionSyntax without goFn should be nil") - } if (&context{}).localTypeOrdinalBySyntax(token.Pos(1)) != 0 { t.Fatal("localTypeOrdinalBySyntax without current function should be zero") } diff --git a/test/goroot/xfail.yaml b/test/goroot/xfail.yaml index 8183574996..125c11cdae 100644 --- a/test/goroot/xfail.yaml +++ b/test/goroot/xfail.yaml @@ -2412,10 +2412,6 @@ xfails: directive: run case: fixedbugs/issue31546.go reason: reflection exposes the initializer value of a blank struct field instead of zero on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue72063.go - reason: generic Y-combinator build exits successfully without producing a binary on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue72844.go