Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cl/_testgo/tpycombinator/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
3628800
xxx
37 changes: 37 additions & 0 deletions cl/_testgo/tpycombinator/in.go
Original file line number Diff line number Diff line change
@@ -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(""))
}
42 changes: 26 additions & 16 deletions cl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,15 +415,21 @@ 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 {
name := v.Name()
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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down
99 changes: 96 additions & 3 deletions cl/funcname_nested_closure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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")
}
Expand Down
4 changes: 0 additions & 4 deletions test/goroot/xfail.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading