diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 88d8e525e..ef3b8aaa9 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -364,6 +364,179 @@ func TestDeclSelection_RemoveUnusedTypeConstraint(t *testing.T) { sel.IsDead(`var:command-line-arguments.ghost`) } +func TestDeclSelection_RemoveUnusedNestedTypesInFunction(t *testing.T) { + src := ` + package main + func Foo[T any](u T) any { + type Bar struct { v T } + return Bar{v: u} + } + func deadCode() { + println(Foo[int](42)) + } + func main() { + println(Foo[string]("cat")) + }` + + srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} + sel := declSelection(t, srcFiles, nil) + sel.IsAlive(`func:command-line-arguments.main`) + + sel.IsAlive(`funcVar:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo`) + sel.IsDead(`func:command-line-arguments.Foo`) + + sel.IsAlive(`typeVar:command-line-arguments.Bar`) + sel.IsAlive(`type:command-line-arguments.Bar`) + sel.IsDead(`type:command-line-arguments.Bar`) + + sel.IsDead(`funcVar:command-line-arguments.deadCode`) + sel.IsDead(`func:command-line-arguments.deadCode`) +} + +func TestDeclSelection_RemoveUnusedNestedTypesInMethod(t *testing.T) { + src := ` + package main + type Baz[T any] struct{} + func (b *Baz[T]) Foo(u T) any { + type Bar struct { v T } + return Bar{v: u} + } + func deadCode() { + b := Baz[int]{} + println(b.Foo(42)) + } + func main() { + b := Baz[string]{} + println(b.Foo("cat")) + }` + + srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} + sel := declSelection(t, srcFiles, nil) + sel.IsAlive(`func:command-line-arguments.main`) + + sel.IsAlive(`typeVar:command-line-arguments.Baz`) + sel.IsDead(`type:command-line-arguments.Baz`) + sel.IsAlive(`type:command-line-arguments.Baz`) + + sel.IsDead(`func:command-line-arguments.(*Baz).Foo`) + sel.IsAlive(`func:command-line-arguments.(*Baz).Foo`) + + sel.IsAlive(`typeVar:command-line-arguments.Bar`) + sel.IsDead(`type:command-line-arguments.Bar`) + sel.IsAlive(`type:command-line-arguments.Bar`) + + sel.IsDead(`funcVar:command-line-arguments.deadCode`) + sel.IsDead(`func:command-line-arguments.deadCode`) +} + +func TestDeclSelection_RemoveAllUnusedNestedTypes(t *testing.T) { + src := ` + package main + func Foo[T any](u T) any { + type Bar struct { v T } + return Bar{v: u} + } + func deadCode() { + println(Foo[int](42)) + println(Foo[string]("cat")) + } + func main() {}` + + srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} + sel := declSelection(t, srcFiles, nil) + sel.IsAlive(`func:command-line-arguments.main`) + + sel.IsDead(`funcVar:command-line-arguments.Foo`) + sel.IsDead(`func:command-line-arguments.Foo`) + sel.IsDead(`func:command-line-arguments.Foo`) + + sel.IsDead(`typeVar:command-line-arguments.Bar`) + sel.IsDead(`type:command-line-arguments.Bar`) + sel.IsDead(`type:command-line-arguments.Bar`) + + sel.IsDead(`funcVar:command-line-arguments.deadCode`) + sel.IsDead(`func:command-line-arguments.deadCode`) +} + +func TestDeclSelection_CompletelyRemoveNestedType(t *testing.T) { + src := ` + package main + func Foo[T any](u T) any { + type Bar struct { v T } + return Bar{v: u} + } + func deadCode() { + println(Foo[int](42)) + } + func main() {}` + + srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} + sel := declSelection(t, srcFiles, nil) + + sel.IsAlive(`func:command-line-arguments.main`) + + sel.IsDead(`funcVar:command-line-arguments.Foo`) + sel.IsDead(`func:command-line-arguments.Foo`) + + sel.IsDead(`typeVar:command-line-arguments.Bar`) + sel.IsDead(`type:command-line-arguments.Bar`) + + sel.IsDead(`funcVar:command-line-arguments.deadCode`) + sel.IsDead(`func:command-line-arguments.deadCode`) +} + +func TestDeclSelection_RemoveAnonNestedTypes(t *testing.T) { + // Based on test/fixedbugs/issue53635.go + // This checks that if an anon type (e.g. []T) is used in a function + // that is not used, the type is removed, otherwise it is kept. + + src := ` + package main + func Foo[T any](u T) any { + return []T(nil) + } + func deadCode() { + println(Foo[string]("cat")) + } + func main() { + println(Foo[int](42)) + }` + + srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} + sel := declSelection(t, srcFiles, nil) + sel.IsDead(`anonType:command-line-arguments.sliceType`) // []string + sel.IsAlive(`anonType:command-line-arguments.sliceType$1`) // []int +} + +func TestDeclSelection_NoNestAppliedToFuncCallInMethod(t *testing.T) { + // Checks that a function call to a non-local function isn't + // being labeled as a nested function call. + src := ` + package main + func foo(a any) { + println(a) + } + type Bar[T any] struct { u T } + func (b *Bar[T]) Baz() { + foo(b.u) + } + func main() { + b := &Bar[int]{u: 42} + b.Baz() + }` + + srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} + sel := declSelection(t, srcFiles, nil) + sel.IsAlive(`init:main`) + + sel.IsAlive(`typeVar:command-line-arguments.Bar`) + sel.IsAlive(`type:command-line-arguments.Bar`) + sel.IsAlive(`func:command-line-arguments.(*Bar).Baz`) + + sel.IsAlive(`func:command-line-arguments.foo`) +} + func TestLengthParenthesizingIssue841(t *testing.T) { // See issue https://github.com/gopherjs/gopherjs/issues/841 // diff --git a/compiler/declNames.go b/compiler/declNames.go index 4ba59e289..57d6ac255 100644 --- a/compiler/declNames.go +++ b/compiler/declNames.go @@ -49,7 +49,7 @@ func funcDeclFullName(inst typeparams.Instance) string { } // typeVarDeclFullName returns a unique name for a package-level variable -// that is used for a named type declaration. +// that is used for a named type declaration or a named nested type declaration. // If the type is generic, this declaration name is also for the list // of instantiations of the type. func typeVarDeclFullName(o *types.TypeName) string { @@ -57,7 +57,9 @@ func typeVarDeclFullName(o *types.TypeName) string { } // typeDeclFullName returns a unique name for a package-level type declaration -// for the given instance of a type. Names are only unique per package. +// for the given instance of a type. Names are only unique per scope package +// unless the type is a nested type then the name is only unique per the +// function or method it is declared in. func typeDeclFullName(inst typeparams.Instance) string { return `type:` + inst.String() } diff --git a/compiler/decls.go b/compiler/decls.go index 5c6291ba6..004973217 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -264,7 +264,7 @@ func (fc *funcContext) newVarDecl(init *types.Initializer) *Decl { fc.localVars = nil // Clean up after ourselves. }) - d.Dce().SetName(init.Lhs[0]) + d.Dce().SetName(init.Lhs[0], nil, nil) if len(init.Lhs) != 1 || analysis.HasSideEffect(init.Rhs, fc.pkgCtx.Info.Info) { d.Dce().SetAsAlive() } @@ -291,7 +291,7 @@ func (fc *funcContext) funcDecls(functions []*ast.FuncDecl) ([]*Decl, error) { FullName: funcVarDeclFullName(o), Vars: []string{objName}, } - varDecl.Dce().SetName(o) + varDecl.Dce().SetName(o, nil, nil) if o.Type().(*types.Signature).TypeParams().Len() != 0 { varDecl.DeclCode = fc.CatchOutput(0, func() { fc.Printf("%s = {};", objName) @@ -331,7 +331,7 @@ func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl, inst typeparams.Instance) Blocking: fc.pkgCtx.IsBlocking(inst), LinkingName: symbol.New(o), } - d.Dce().SetName(o, inst.TArgs...) + d.Dce().SetName(o, inst.TNest, inst.TArgs) if typesutil.IsMethod(o) { recv := typesutil.RecvType(o.Type().(*types.Signature)).Obj() @@ -433,6 +433,7 @@ func (fc *funcContext) newNamedTypeVarDecl(obj *types.TypeName) *Decl { FullName: typeVarDeclFullName(obj), Vars: []string{name}, } + varDecl.Dce().SetName(obj, nil, nil) if fc.pkgCtx.instanceSet.Pkg(obj.Pkg()).ObjHasInstances(obj) { varDecl.DeclCode = fc.CatchOutput(0, func() { fc.Printf("%s = {};", name) @@ -470,7 +471,7 @@ func (fc *funcContext) newNamedTypeInstDecl(inst typeparams.Instance) (*Decl, er d := &Decl{ FullName: typeDeclFullName(inst), } - d.Dce().SetName(inst.Object, inst.TArgs...) + d.Dce().SetName(inst.Object, inst.TNest, inst.TArgs) fc.pkgCtx.CollectDCEDeps(d, func() { // Code that declares a JS type (i.e. prototype) for each Go type. d.DeclCode = fc.CatchOutput(0, func() { @@ -595,7 +596,7 @@ func (fc *funcContext) anonTypeDecls(anonTypes []*types.TypeName) []*Decl { FullName: anonTypeDeclFullName(t), Vars: []string{t.Name()}, } - d.Dce().SetName(t) + d.Dce().SetName(t, nil, nil) fc.pkgCtx.CollectDCEDeps(d, func() { d.DeclCode = []byte(fmt.Sprintf("\t%s = $%sType(%s);\n", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), fc.initArgs(t.Type()))) }) diff --git a/compiler/expressions.go b/compiler/expressions.go index 455549a1d..6f8ab6e2b 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -591,7 +591,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { case types.MethodVal: return fc.formatExpr(`$methodVal(%s, "%s")`, fc.makeReceiver(e), sel.Obj().(*types.Func).Name()) case types.MethodExpr: - fc.pkgCtx.DeclareDCEDep(sel.Obj(), inst.TArgs...) + fc.pkgCtx.DeclareDCEDep(sel.Obj(), inst.TNest, inst.TArgs) if _, ok := sel.Recv().Underlying().(*types.Interface); ok { return fc.formatExpr(`$ifaceMethodExpr("%s")`, sel.Obj().(*types.Func).Name()) } @@ -906,7 +906,7 @@ func (fc *funcContext) delegatedCall(expr *ast.CallExpr) (callable *expression, func (fc *funcContext) makeReceiver(e *ast.SelectorExpr) *expression { sel, _ := fc.selectionOf(e) if !sel.Obj().Exported() { - fc.pkgCtx.DeclareDCEDep(sel.Obj()) + fc.pkgCtx.DeclareDCEDep(sel.Obj(), nil, nil) } x := e.X diff --git a/compiler/internal/dce/README.md b/compiler/internal/dce/README.md index 01ec1e8c6..209b51d68 100644 --- a/compiler/internal/dce/README.md +++ b/compiler/internal/dce/README.md @@ -265,17 +265,32 @@ of either will indicate a use of both. This will cause slightly more unexported methods to be alive while reducing the complication of type checking which object or type of object is performing the call. -| Declaration | exported | unexported | non-generic | generic | object name | method name | -|:------------|:--------:|:----------:|:-----------:|:-------:|:------------|:------------| -| variables | █ | █ | █ | n/a | `.` | | -| functions | █ | █ | █ | | `.` | | -| functions | █ | █ | | █ | `.[]` | | -| named type | █ | █ | █ | | `.` | | -| named type | █ | █ | | █ | `.[]` | | -| method | █ | | █ | | `.` | | -| method | █ | | | █ | `.[]` | | -| method | | █ | █ | | `.` | `.()()` | -| method | | █ | | █ | `.[]` | `.()()` | +| Declaration | exported | unexported | generic | object name | method name | +|:------------|:--------:|:----------:|:-------:|:------------|:------------| +| variables | █ | █ | | `.` | | +| functions | █ | █ | | `.` | | +| functions | █ | █ | █ | `.[]` | | +| named type | █ | █ | | `.` | | +| named type | █ | █ | █ | `.[]` | | +| method | █ | | | `.` | | +| method | █ | | █ | `.[]` | | +| method | | █ | | `.` | `.()()` | +| method | | █ | █ | `.[]` | `.()()` | + +For nested types, the nest (the function or method in which the type is +declared) needs to be taken into consideration to differentiate types with +the same name but different nests. + +| Nest type | generic nest | generic object | object name | +|:----------|:------------:|:--------------:|:------------| +| function | | | `.:` | +| function | █ | | `.:[;]` | +| function | | █ | `.:[]` | +| function | █ | █ | `.:[;]` | +| method | | | `.::` | +| method | █ | | `.::[;]` | +| method | | █ | `.::[]` | +| method | █ | █ | `.::[;]` | #### Name Specifics diff --git a/compiler/internal/dce/collector.go b/compiler/internal/dce/collector.go index fea52468d..3ddac5e8b 100644 --- a/compiler/internal/dce/collector.go +++ b/compiler/internal/dce/collector.go @@ -39,8 +39,8 @@ func (c *Collector) CollectDCEDeps(decl Decl, f func()) { // The given optional type arguments are used to when the object is a // function with type parameters or anytime the object doesn't carry them. // If not given, this attempts to get the type arguments from the object. -func (c *Collector) DeclareDCEDep(o types.Object, tArgs ...types.Type) { +func (c *Collector) DeclareDCEDep(o types.Object, tNest, tArgs []types.Type) { if c.dce != nil { - c.dce.addDep(o, tArgs) + c.dce.addDep(o, tNest, tArgs) } } diff --git a/compiler/internal/dce/dce_test.go b/compiler/internal/dce/dce_test.go index 3ddeac848..849596a37 100644 --- a/compiler/internal/dce/dce_test.go +++ b/compiler/internal/dce/dce_test.go @@ -43,34 +43,34 @@ func Test_Collector_Collecting(t *testing.T) { decl2 := quickTestDecl(obj2) var c Collector - c.DeclareDCEDep(obj1) // no effect since a collection isn't running. + c.DeclareDCEDep(obj1, nil, nil) // no effect since a collection isn't running. depCount(t, decl1, 0) depCount(t, decl2, 0) c.CollectDCEDeps(decl1, func() { - c.DeclareDCEDep(obj2) - c.DeclareDCEDep(obj3) - c.DeclareDCEDep(obj3) // already added so has no effect. + c.DeclareDCEDep(obj2, nil, nil) + c.DeclareDCEDep(obj3, nil, nil) + c.DeclareDCEDep(obj3, nil, nil) // already added so has no effect. }) depCount(t, decl1, 2) depCount(t, decl2, 0) - c.DeclareDCEDep(obj4) // no effect since a collection isn't running. + c.DeclareDCEDep(obj4, nil, nil) // no effect since a collection isn't running. depCount(t, decl1, 2) depCount(t, decl2, 0) c.CollectDCEDeps(decl2, func() { - c.DeclareDCEDep(obj5) - c.DeclareDCEDep(obj6) - c.DeclareDCEDep(obj7) + c.DeclareDCEDep(obj5, nil, nil) + c.DeclareDCEDep(obj6, nil, nil) + c.DeclareDCEDep(obj7, nil, nil) }) depCount(t, decl1, 2) depCount(t, decl2, 3) // The second collection adds to existing dependencies. c.CollectDCEDeps(decl2, func() { - c.DeclareDCEDep(obj4) - c.DeclareDCEDep(obj5) + c.DeclareDCEDep(obj4, nil, nil) + c.DeclareDCEDep(obj5, nil, nil) }) depCount(t, decl1, 2) depCount(t, decl2, 4) @@ -541,7 +541,7 @@ func Test_Info_SetNameAndDep(t *testing.T) { equal(t, d.Dce().String(), `[unnamed] -> []`) t.Log(`object:`, types.ObjectString(tt.obj, nil)) - d.Dce().SetName(tt.obj) + d.Dce().SetName(tt.obj, nil, nil) equal(t, d.Dce().unnamed(), tt.want.unnamed()) equal(t, d.Dce().objectFilter, tt.want.objectFilter) equal(t, d.Dce().methodFilter, tt.want.methodFilter) @@ -568,7 +568,7 @@ func Test_Info_SetNameAndDep(t *testing.T) { c := Collector{} c.CollectDCEDeps(d, func() { - c.DeclareDCEDep(tt.obj) + c.DeclareDCEDep(tt.obj, nil, nil) }) equalSlices(t, d.Dce().getDeps(), wantDeps) }) @@ -582,10 +582,10 @@ func Test_Info_SetNameOnlyOnce(t *testing.T) { obj2 := quickVar(pkg, `Stripe`) decl := &testDecl{} - decl.Dce().SetName(obj1) + decl.Dce().SetName(obj1, nil, nil) err := capturePanic(t, func() { - decl.Dce().SetName(obj2) + decl.Dce().SetName(obj2, nil, nil) }) errorMatches(t, err, `^may only set the name once for path/to/mogwai\.Gizmo .*$`) } @@ -743,7 +743,7 @@ func Test_Info_UsesDeps(t *testing.T) { c := Collector{} c.CollectDCEDeps(d, func() { - c.DeclareDCEDep(uses, tArgs...) + c.DeclareDCEDep(uses, nil, tArgs) }) equalSlices(t, d.Dce().getDeps(), tt.wantDeps) }) @@ -752,10 +752,11 @@ func Test_Info_UsesDeps(t *testing.T) { func Test_Info_SpecificCasesDeps(t *testing.T) { tests := []struct { - name string - obj types.Object - tArgs []types.Type - wantDeps []string + name string + obj types.Object + nestTArgs []types.Type + tArgs []types.Type + wantDeps []string }{ { name: `struct instantiation with generic object`, @@ -808,16 +809,113 @@ func Test_Info_SpecificCasesDeps(t *testing.T) { `astoria.shuffle(string) int`, }, }, + { + name: `a concrete function with a nested concrete type instance`, + obj: parseObject(t, `davi`, + `package astoria + func jake(v int) any { + type davi struct { V int } + return davi{ V: v } + }`), + wantDeps: []string{`astoria.jake:davi`}, + }, + { + name: `a concrete function with a nested generic type instance`, + obj: parseObject(t, `pantoliano`, + `package astoria + func francis(v int) any { + type pantoliano[T any] struct { V int } + return pantoliano[int]{ V: v } + }`), + tArgs: []types.Type{types.Typ[types.Int]}, + wantDeps: []string{`astoria.francis:pantoliano[int]`}, + }, + { + name: `a generic function with a nested concrete type instance`, + obj: parseObject(t, `ramsey`, + `package astoria + func mama[T any](v T) any { + type ramsey struct { V T } + return ramsey{ V: v } + }`), + nestTArgs: []types.Type{types.Typ[types.Int]}, + wantDeps: []string{`astoria.mama:ramsey[int;]`}, + }, + { + name: `a generic function with a nested generic type instance`, + obj: parseObject(t, `matuszak`, + `package astoria + func sloth[T any]() any { + type matuszak[U any] struct { X T; Y U } + return matuszak[bool]{} + }`), + nestTArgs: []types.Type{types.Typ[types.String]}, + tArgs: []types.Type{types.Typ[types.Bool]}, + wantDeps: []string{`astoria.sloth:matuszak[string; bool]`}, + }, + { + name: `a concrete method with a nested concrete type instance`, + obj: parseObject(t, `davi`, + `package astoria + type fratelli struct { V int } + func (m *fratelli) jake() any { + type davi struct { V int } + return davi{ V: m.V } + }`), + wantDeps: []string{`astoria.fratelli:jake:davi`}, + }, + { + name: `a concrete method with a nested generic type instance`, + obj: parseObject(t, `pantoliano`, + `package astoria + type fratelli struct { V int } + func (f *fratelli) francis(v int) any { + type pantoliano[T any] struct { V int } + return pantoliano[int]{ V: v } + }`), + tArgs: []types.Type{types.Typ[types.Int]}, + wantDeps: []string{`astoria.fratelli:francis:pantoliano[int]`}, + }, + { + name: `a generic method with a nested concrete type instance`, + obj: parseObject(t, `ramsey`, + `package astoria + type fratelli[T any] struct { v T } + func (f *fratelli[T]) mama() any { + type ramsey struct { V T } + return ramsey{ V: f.v } + }`), + nestTArgs: []types.Type{types.Typ[types.Int]}, + wantDeps: []string{`astoria.fratelli:mama:ramsey[int;]`}, + }, + { + name: `a generic method with a nested generic type instance`, + obj: parseObject(t, `matuszak`, + `package astoria + type fratelli[T any] struct {} + func (f *fratelli[T]) sloth() any { + type matuszak[U any] struct { X T; Y U } + return matuszak[bool]{} + }`), + nestTArgs: []types.Type{types.Typ[types.String]}, + tArgs: []types.Type{types.Typ[types.Bool]}, + wantDeps: []string{`astoria.fratelli:sloth:matuszak[string; bool]`}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := &testDecl{} - t.Logf(`object: %s with [%s]`, types.ObjectString(tt.obj, nil), (typesutil.TypeList)(tt.tArgs).String()) + tail := `` + if len(tt.nestTArgs) > 0 { + tail += (typesutil.TypeList)(tt.nestTArgs).String() + `;` + } + tail += (typesutil.TypeList)(tt.tArgs).String() + t.Logf(`object: %s with [%s]`, types.ObjectString(tt.obj, nil), tail) c := Collector{} c.CollectDCEDeps(d, func() { - c.DeclareDCEDep(tt.obj, tt.tArgs...) + c.DeclareDCEDep(tt.obj, tt.nestTArgs, tt.tArgs) }) equalSlices(t, d.Dce().getDeps(), tt.wantDeps) }) @@ -837,7 +935,7 @@ func Test_Info_SetAsAlive(t *testing.T) { equal(t, decl.Dce().isAlive(), true) // still alive but now explicitly alive equal(t, decl.Dce().String(), `[alive] [unnamed] -> []`) - decl.Dce().SetName(obj) + decl.Dce().SetName(obj, nil, nil) equal(t, decl.Dce().isAlive(), true) // alive because SetAsAlive was called equal(t, decl.Dce().String(), `[alive] path/to/fantasia.Falkor -> []`) }) @@ -848,7 +946,7 @@ func Test_Info_SetAsAlive(t *testing.T) { equal(t, decl.Dce().isAlive(), true) // unnamed is automatically alive equal(t, decl.Dce().String(), `[unnamed] -> []`) - decl.Dce().SetName(obj) + decl.Dce().SetName(obj, nil, nil) equal(t, decl.Dce().isAlive(), false) // named so no longer automatically alive equal(t, decl.Dce().String(), `path/to/fantasia.Artax -> []`) @@ -876,27 +974,27 @@ func Test_Selector_JustVars(t *testing.T) { c := Collector{} c.CollectDCEDeps(frodo, func() { - c.DeclareDCEDep(samwise.obj) - c.DeclareDCEDep(meri.obj) - c.DeclareDCEDep(pippin.obj) + c.DeclareDCEDep(samwise.obj, nil, nil) + c.DeclareDCEDep(meri.obj, nil, nil) + c.DeclareDCEDep(pippin.obj, nil, nil) }) c.CollectDCEDeps(pippin, func() { - c.DeclareDCEDep(meri.obj) + c.DeclareDCEDep(meri.obj, nil, nil) }) c.CollectDCEDeps(aragorn, func() { - c.DeclareDCEDep(boromir.obj) + c.DeclareDCEDep(boromir.obj, nil, nil) }) c.CollectDCEDeps(gimli, func() { - c.DeclareDCEDep(legolas.obj) + c.DeclareDCEDep(legolas.obj, nil, nil) }) c.CollectDCEDeps(legolas, func() { - c.DeclareDCEDep(gimli.obj) + c.DeclareDCEDep(gimli.obj, nil, nil) }) c.CollectDCEDeps(gandalf, func() { - c.DeclareDCEDep(frodo.obj) - c.DeclareDCEDep(aragorn.obj) - c.DeclareDCEDep(gimli.obj) - c.DeclareDCEDep(legolas.obj) + c.DeclareDCEDep(frodo.obj, nil, nil) + c.DeclareDCEDep(aragorn.obj, nil, nil) + c.DeclareDCEDep(gimli.obj, nil, nil) + c.DeclareDCEDep(legolas.obj, nil, nil) }) for _, decl := range fellowship { @@ -1012,16 +1110,16 @@ func Test_Selector_SpecificMethods(t *testing.T) { c := Collector{} c.CollectDCEDeps(rincewindRun, func() { - c.DeclareDCEDep(rincewind.obj) + c.DeclareDCEDep(rincewind.obj, nil, nil) }) c.CollectDCEDeps(rincewindHide, func() { - c.DeclareDCEDep(rincewind.obj) + c.DeclareDCEDep(rincewind.obj, nil, nil) }) c.CollectDCEDeps(vimesRun, func() { - c.DeclareDCEDep(vimes.obj) + c.DeclareDCEDep(vimes.obj, nil, nil) }) c.CollectDCEDeps(vimesRead, func() { - c.DeclareDCEDep(vimes.obj) + c.DeclareDCEDep(vimes.obj, nil, nil) }) vetinari.Dce().SetAsAlive() @@ -1058,7 +1156,7 @@ func Test_Selector_SpecificMethods(t *testing.T) { vetinari.Dce().deps = nil // reset deps c.CollectDCEDeps(vetinari, func() { for _, decl := range tt.deps { - c.DeclareDCEDep(decl.obj) + c.DeclareDCEDep(decl.obj, nil, nil) } }) @@ -1095,7 +1193,7 @@ func testPackage(name string) *types.Package { func quickTestDecl(o types.Object) *testDecl { d := &testDecl{obj: o} - d.Dce().SetName(o) + d.Dce().SetName(o, nil, nil) return d } diff --git a/compiler/internal/dce/filters.go b/compiler/internal/dce/filters.go index 420fd4310..c074fe545 100644 --- a/compiler/internal/dce/filters.go +++ b/compiler/internal/dce/filters.go @@ -5,6 +5,9 @@ import ( "sort" "strconv" "strings" + + "github.com/gopherjs/gopherjs/compiler/internal/typeparams" + "github.com/gopherjs/gopherjs/compiler/typesutil" ) // getFilters determines the DCE filters for the given object. @@ -16,7 +19,7 @@ import ( // the object filter will be empty and only the method filter will be set. // The later shouldn't happen when naming a declaration but only when creating // dependencies. -func getFilters(o types.Object, tArgs []types.Type) (objectFilter, methodFilter string) { +func getFilters(o types.Object, nestTArgs, tArgs []types.Type) (objectFilter, methodFilter string) { if f, ok := o.(*types.Func); ok { sig := f.Type().(*types.Signature) if recv := sig.Recv(); recv != nil { @@ -30,7 +33,7 @@ func getFilters(o types.Object, tArgs []types.Type) (objectFilter, methodFilter tArgs = getTypeArgs(typ) } if named, ok := typ.(*types.Named); ok { - objectFilter = getObjectFilter(named.Obj(), tArgs) + objectFilter = getObjectFilter(named.Obj(), nil, tArgs) } // The method is not exported so we only need the method filter. @@ -42,7 +45,7 @@ func getFilters(o types.Object, tArgs []types.Type) (objectFilter, methodFilter } // The object is not a method so we only need the object filter. - objectFilter = getObjectFilter(o, tArgs) + objectFilter = getObjectFilter(o, nestTArgs, tArgs) return } @@ -51,8 +54,8 @@ func getFilters(o types.Object, tArgs []types.Type) (objectFilter, methodFilter // See [naming design] for more information. // // [naming design]: https://github.com/gopherjs/gopherjs/compiler/internal/dce/README.md#naming -func getObjectFilter(o types.Object, tArgs []types.Type) string { - return (&filterGen{argTypeRemap: tArgs}).Object(o, tArgs) +func getObjectFilter(o types.Object, nestTArgs, tArgs []types.Type) string { + return (&filterGen{}).Object(o, nestTArgs, tArgs) } // getMethodFilter returns the method filter that functions as the secondary @@ -62,39 +65,74 @@ func getObjectFilter(o types.Object, tArgs []types.Type) string { // [naming design]: https://github.com/gopherjs/gopherjs/compiler/internal/dce/README.md#naming func getMethodFilter(o types.Object, tArgs []types.Type) string { if sig, ok := o.Type().(*types.Signature); ok { + gen := &filterGen{} + tParams := getTypeParams(o.Type()) if len(tArgs) == 0 { - if recv := sig.Recv(); recv != nil { - tArgs = getTypeArgs(recv.Type()) - } + tArgs = getTypeArgs(sig) + } + if len(tArgs) > 0 { + gen.addReplacements(tParams, tArgs) } - gen := &filterGen{argTypeRemap: tArgs} return objectName(o) + gen.Signature(sig) } return `` } // objectName returns the name part of a filter name, -// including the package path, if available. +// including the package path and nest names, if available. // // This is different from `o.Id` since it always includes the package path // when available and doesn't add "_." when not available. func objectName(o types.Object) string { + prefix := `` if o.Pkg() != nil { - return o.Pkg().Path() + `.` + o.Name() + prefix += o.Pkg().Path() + `.` + } + if nest := typeparams.FindNestingFunc(o); nest != nil && nest != o { + if recv := typesutil.RecvType(nest.Type().(*types.Signature)); recv != nil { + prefix += recv.Obj().Name() + `:` + } + prefix += nest.Name() + `:` + } + return prefix + o.Name() +} + +// getNestTypeParams gets the type parameters for the nesting function +// or nil if the object is not nested in a function or +// the given object is a function itself. +func getNestTypeParams(o types.Object) []types.Type { + fn := typeparams.FindNestingFunc(o) + if fn == nil || fn == o { + return nil + } + + tp := typeparams.SignatureTypeParams(fn.Type().(*types.Signature)) + nestTParams := make([]types.Type, tp.Len()) + for i := 0; i < tp.Len(); i++ { + nestTParams[i] = tp.At(i) } - return o.Name() + return nestTParams } // getTypeArgs gets the type arguments for the given type -// wether they are type arguments or type parameters. +// or nil if the type does not have type arguments. func getTypeArgs(typ types.Type) []types.Type { switch t := typ.(type) { case *types.Pointer: return getTypeArgs(t.Elem()) case *types.Named: - if typeArgs := t.TypeArgs(); typeArgs != nil { - return typeListToSlice(typeArgs) - } + return typeListToSlice(t.TypeArgs()) + } + return nil +} + +// getTypeParams gets the type parameters for the given type +// or nil if the type does not have type parameters. +func getTypeParams(typ types.Type) []types.Type { + switch t := typ.(type) { + case *types.Pointer: + return getTypeParams(t.Elem()) + case *types.Named: if typeParams := t.TypeParams(); typeParams != nil { return typeParamListToSlice(typeParams) } @@ -122,22 +160,28 @@ func typeListToSlice(typeArgs *types.TypeList) []types.Type { func typeParamListToSlice(typeParams *types.TypeParamList) []types.Type { tParams := make([]types.Type, typeParams.Len()) for i := range tParams { - tParams[i] = typeParams.At(i).Constraint() + tParams[i] = typeParams.At(i) } return tParams } type processingGroup struct { - o types.Object - tArgs []types.Type + o types.Object + nestTArgs []types.Type + tArgs []types.Type } -func (p processingGroup) is(o types.Object, tArgs []types.Type) bool { - if len(p.tArgs) != len(tArgs) || p.o != o { +func (p processingGroup) is(o types.Object, nestTArgs, tArgs []types.Type) bool { + if len(p.nestTArgs) != len(nestTArgs) || len(p.tArgs) != len(tArgs) || p.o != o { return false } - for i, tArg := range tArgs { - if p.tArgs[i] != tArg { + for i, ta := range nestTArgs { + if p.nestTArgs[i] != ta { + return false + } + } + for i, ta := range tArgs { + if p.tArgs[i] != ta { return false } } @@ -145,20 +189,79 @@ func (p processingGroup) is(o types.Object, tArgs []types.Type) bool { } type filterGen struct { - // argTypeRemap is the type arguments in the same order as the - // type parameters in the top level object such that the type parameters - // index can be used to get the type argument. - argTypeRemap []types.Type - inProgress []processingGroup + // replacement is used to use another type in place of a given type + // this is typically used for type parameters to type arguments. + replacement map[types.Type]types.Type + inProgress []processingGroup +} + +// addReplacements adds a mapping from one type to another. +// The slices should be the same length but will ignore any extra types. +// Any replacement where the key and value are the same will be ignored. +func (gen *filterGen) addReplacements(from []types.Type, to []types.Type) { + if len(from) == 0 || len(to) == 0 { + return + } + + if gen.replacement == nil { + gen.replacement = map[types.Type]types.Type{} + } + + count := len(from) + if count > len(to) { + count = len(to) + } + for i := 0; i < count; i++ { + if from[i] != to[i] { + gen.replacement[from[i]] = to[i] + } + } } -func (gen *filterGen) startProcessing(o types.Object, tArgs []types.Type) bool { +// pushGenerics prepares the filter generator for processing an object +// by setting any generic information and nesting information needed for it. +// It returns the type arguments for the object and a function to restore +// the previous state of the filter generator. +func (gen *filterGen) pushGenerics(o types.Object, nestTArgs, tArgs []types.Type) ([]types.Type, []types.Type, func()) { + // Create a new replacement map and copy the old one into it. + oldReplacement := gen.replacement + gen.replacement = map[types.Type]types.Type{} + for tp, ta := range oldReplacement { + gen.replacement[tp] = ta + } + + // Prepare the nested context for the object. + nestTParams := getNestTypeParams(o) + if len(nestTArgs) > 0 { + gen.addReplacements(nestTParams, nestTArgs) + } else { + nestTArgs = nestTParams + } + + // Prepare the type arguments for the object. + tParams := getTypeParams(o.Type()) + if len(tArgs) == 0 { + tArgs = getTypeArgs(o.Type()) + } + if len(tArgs) > 0 { + gen.addReplacements(tParams, tArgs) + } else { + tArgs = tParams + } + + // Return a function to restore the old replacement map. + return nestTArgs, tArgs, func() { + gen.replacement = oldReplacement + } +} + +func (gen *filterGen) startProcessing(o types.Object, nestTArgs, tArgs []types.Type) bool { for _, p := range gen.inProgress { - if p.is(o, tArgs) { + if p.is(o, nestTArgs, tArgs) { return false } } - gen.inProgress = append(gen.inProgress, processingGroup{o, tArgs}) + gen.inProgress = append(gen.inProgress, processingGroup{o: o, nestTArgs: nestTArgs, tArgs: tArgs}) return true } @@ -167,19 +270,19 @@ func (gen *filterGen) stopProcessing() { } // Object returns an object filter or filter part for an object. -func (gen *filterGen) Object(o types.Object, tArgs []types.Type) string { +func (gen *filterGen) Object(o types.Object, nestTArgs, tArgs []types.Type) string { filter := objectName(o) // Add additional type information for generics and instances. - if len(tArgs) == 0 { - tArgs = getTypeArgs(o.Type()) - } - if len(tArgs) > 0 { + nestTArgs, tArgs, popGenerics := gen.pushGenerics(o, nestTArgs, tArgs) + defer popGenerics() + + if len(tArgs) > 0 || len(nestTArgs) > 0 { // Avoid infinite recursion in type arguments by // tracking the current object and type arguments being processed // and skipping if already in progress. - if gen.startProcessing(o, tArgs) { - filter += gen.TypeArgs(tArgs) + if gen.startProcessing(o, nestTArgs, tArgs) { + filter += gen.TypeArgs(nestTArgs, tArgs) gen.stopProcessing() } else { filter += `[...]` @@ -205,13 +308,24 @@ func (gen *filterGen) Signature(sig *types.Signature) string { } // TypeArgs returns the filter part containing the type -// arguments, e.g. `[any,int|string]`. -func (gen *filterGen) TypeArgs(tArgs []types.Type) string { - parts := make([]string, len(tArgs)) - for i, tArg := range tArgs { - parts[i] = gen.Type(tArg) +// arguments, e.g. `[bool;any,int|string]`. +func (gen *filterGen) TypeArgs(nestTArgs, tArgs []types.Type) string { + toStr := func(t []types.Type) string { + parts := make([]string, len(t)) + for i, ta := range t { + parts[i] = gen.Type(ta) + } + return strings.Join(parts, `, `) + } + + head := `[` + if len(nestTArgs) > 0 { + head += toStr(nestTArgs) + `;` + if len(tArgs) > 0 { + head += ` ` + } } - return `[` + strings.Join(parts, `, `) + `]` + return head + toStr(tArgs) + `]` } // Tuple returns the filter part containing parameter or result @@ -236,9 +350,6 @@ func (gen *filterGen) Tuple(t *types.Tuple, variadic bool) string { // Type returns the filter part for a single type. func (gen *filterGen) Type(typ types.Type) string { switch t := typ.(type) { - case types.Object: - return gen.Object(t, nil) - case *types.Array: return `[` + strconv.FormatInt(t.Len(), 10) + `]` + gen.Type(t.Elem()) case *types.Chan: @@ -248,8 +359,8 @@ func (gen *filterGen) Type(typ types.Type) string { case *types.Map: return `map[` + gen.Type(t.Key()) + `]` + gen.Type(t.Elem()) case *types.Named: - // Get type args from named instance not generic object - return gen.Object(t.Obj(), getTypeArgs(t)) + // Get type args from named instance not generic object. + return gen.Object(t.Obj(), nil, typeListToSlice(t.TypeArgs())) case *types.Pointer: return `*` + gen.Type(t.Elem()) case *types.Signature: @@ -328,14 +439,10 @@ func (gen *filterGen) Struct(s *types.Struct) string { } // TypeParam returns the filter part for a type parameter. -// If there is an argument remap, it will use the remapped type -// so long as it doesn't map to itself. +// If there is an argument remap, it will use the remapped type. func (gen *filterGen) TypeParam(t *types.TypeParam) string { - index := t.Index() - if index >= 0 && index < len(gen.argTypeRemap) { - if inst := gen.argTypeRemap[index]; inst != t { - return gen.Type(inst) - } + if inst, exists := gen.replacement[t]; exists { + return gen.Type(inst) } if t.Constraint() == nil { return `any` diff --git a/compiler/internal/dce/info.go b/compiler/internal/dce/info.go index 6a45e9ef3..ef310f047 100644 --- a/compiler/internal/dce/info.go +++ b/compiler/internal/dce/info.go @@ -81,19 +81,19 @@ func (d *Info) SetAsAlive() { // The given optional type arguments are used to when the object is a // function with type parameters or anytime the object doesn't carry them. // If not given, this attempts to get the type arguments from the object. -func (d *Info) SetName(o types.Object, tArgs ...types.Type) { +func (d *Info) SetName(o types.Object, tNest, tArgs []types.Type) { if !d.unnamed() { panic(fmt.Errorf(`may only set the name once for %s`, d.String())) } // Determine name(s) for DCE. - d.objectFilter, d.methodFilter = getFilters(o, tArgs) + d.objectFilter, d.methodFilter = getFilters(o, tNest, tArgs) } // addDep add a declaration dependencies used by DCE // for the declaration this DCE info is attached to. -func (d *Info) addDep(o types.Object, tArgs []types.Type) { - objectFilter, methodFilter := getFilters(o, tArgs) +func (d *Info) addDep(o types.Object, tNest, tArgs []types.Type) { + objectFilter, methodFilter := getFilters(o, tNest, tArgs) d.addDepName(objectFilter) d.addDepName(methodFilter) } diff --git a/compiler/internal/typeparams/utils.go b/compiler/internal/typeparams/utils.go index 9fcf53c36..27fab0a89 100644 --- a/compiler/internal/typeparams/utils.go +++ b/compiler/internal/typeparams/utils.go @@ -21,24 +21,71 @@ func SignatureTypeParams(sig *types.Signature) *types.TypeParamList { } // FindNestingFunc returns the function or method that the given object -// is nested in, or nil if the object was defined at the package level. +// is nested in. Returns nil if the object was defined at the package level, +// the position is invalid, or if the object is a function or method. +// +// This may get different results for some specific object types +// (e.g. receivers, type parameters) depending on the Go version. +// In go1.19 and earlier, some objects are not nested in the function +// they are part of the definition of, but in later versions they are. func FindNestingFunc(obj types.Object) *types.Func { + if obj == nil { + return nil + } objPos := obj.Pos() if objPos == token.NoPos { return nil } - // We can't use `obj.Parent()` here since some types don't have a set - // parent, such as types created with `types.NewTypeName`. Instead find - // the innermost scope from the package to use as the object's parent scope. - scope := obj.Pkg().Scope().Innermost(objPos) + if _, ok := obj.(*types.Func); ok { + // Functions and methods are not nested in any other function. + return nil + } + + var pkgScope *types.Scope + if obj.Pkg() != nil { + pkgScope = obj.Pkg().Scope() + } + scope := obj.Parent() + if scope == nil { + // Some types have a nil parent scope, such as methods and field, and + // types created with `types.NewTypeName`. Instead find the innermost + // scope from the package to use as the object's parent scope. + if pkgScope == nil { + return nil + } + scope = pkgScope.Innermost(objPos) + } + + if scope == pkgScope { + // If the object is defined at the package level, + // we can shortcut this check and just return nil. + return nil + } + + // Walk up the scope chain to find the function or method that contains + // the object at the given position. for scope != nil { - // Iterate over all declarations in the scope. + // Iterate over all objects declared in the scope. for _, name := range scope.Names() { - decl := scope.Lookup(name) - if fn, ok := decl.(*types.Func); ok && fn.Scope().Contains(objPos) { + d := scope.Lookup(name) + if fn, ok := d.(*types.Func); ok && fn.Scope() != nil && fn.Scope().Contains(objPos) { return fn } + + if named, ok := d.Type().(*types.Named); ok { + // Iterate over all methods of an object. + for i := 0; i < named.NumMethods(); i++ { + if m := named.Method(i); m != nil && m.Scope() != nil && m.Scope().Contains(objPos) { + return m + } + } + } + } + if scope == pkgScope { + // If we reached the package scope, stop searching. + // We don't need to check the universal scope. + break } scope = scope.Parent() } diff --git a/compiler/internal/typeparams/utils_test.go b/compiler/internal/typeparams/utils_test.go index dda685273..1e10e963a 100644 --- a/compiler/internal/typeparams/utils_test.go +++ b/compiler/internal/typeparams/utils_test.go @@ -2,10 +2,13 @@ package typeparams import ( "errors" + "fmt" + "go/ast" "go/token" "go/types" "testing" + "github.com/google/go-cmp/cmp" "github.com/gopherjs/gopherjs/internal/srctesting" ) @@ -118,3 +121,165 @@ func TestRequiresGenericsSupport(t *testing.T) { } }) } + +func Test_FindNestingFunc(t *testing.T) { + src := `package main + + type bob int + func (a bob) riker() any { + type bill struct{ b int } + return bill{b: int(a)} + } + + type milo[T any] struct{} + func (c *milo[U]) mario() any { + type homer struct{ d U } + return homer{} + } + + func bart() any { + e := []milo[int]{{}} + f := &e[0] + return f.mario() + } + + func jack() any { + type linus interface{ + interface { + marvin() + } + luke() + } + type owen interface { + linus + isaac() + } + return owen(nil) + } + + func bender() any { + charles := func() any { + type arthur struct{ h int } + return arthur{h: 42} + } + return charles() + } + + var ned = func() any { + type elmer struct{ i int } + return elmer{i: 42} + }() + + func garfield(count int) { + calvin: + for j := 0; j < count; j++ { + if j == 5 { + break calvin + } + } + }` + + f := srctesting.New(t) + file := f.Parse("main.go", src) + info, _ := f.Check("test", file) + + // Collect all objects and find nesting functions. + // The results will be ordered by position in the file. + results := []string{} + ast.Inspect(file, func(n ast.Node) bool { + if id, ok := n.(*ast.Ident); ok { + obj := info.ObjectOf(id) + if _, isVar := obj.(*types.Var); isVar { + // Skip variables, some variables (e.g. receivers) are not inside + // a function's scope in go1.19 but in later versions they are. + return true + } + if named, isNamed := obj.(*types.TypeName); isNamed { + if _, isTP := named.Type().(*types.TypeParam); isTP { + // Skip type parameters since they are not inside + // a function's scope in go1.19 but in later versions they are. + return true + } + } + + fn := FindNestingFunc(obj) + fnName := `` + if fn != nil { + fnName = fn.FullName() + } + results = append(results, fmt.Sprintf("%3d) %s => %s", id.Pos(), id.Name, fnName)) + } + return true + }) + + diff := cmp.Diff([]string{ + // package main (nil object) + ` 9) main => `, + + // type bob int + ` 22) bob => `, + ` 26) int => `, // use of basic + + // func (a bob) riker() any { ... } + ` 40) bob => `, + ` 45) riker => `, + ` 53) any => `, + ` 67) bill => (test.bob).riker`, // def + ` 82) int => `, + ` 98) bill => (test.bob).riker`, // use + `106) int => `, + + // type milo[T any] struct {} + `126) milo => `, + `133) any => `, + + // func (c *milo[U]) mario() any { ... } + `158) milo => `, + `167) mario => `, + `175) any => `, + `189) homer => (*test.milo[U]).mario`, // def + `219) homer => (*test.milo[U]).mario`, // use + + // func bart() any { ... } + `239) bart => `, + `246) any => `, + `262) milo => `, // use of non-local defined type + `267) int => `, + `302) mario => `, // use of method on non-local defined type + + // func jack() any { ... } + `322) jack => `, + `329) any => `, + `343) linus => test.jack`, // def + `381) marvin => `, // method def + `400) luke => `, // method def + `420) owen => test.jack`, // def + `441) linus => test.jack`, // use + `451) isaac => `, // method def + `474) owen => test.jack`, // use + `479) nil => `, // use of nil + + // func bender() any { ... } + `496) bender => `, + `505) any => `, + `532) any => `, + `547) arthur => test.bender`, // def inside func lit + `564) int => `, + `581) arthur => test.bender`, // use + + // var ned = func() any { ... } + `646) any => `, + `660) elmer => `, // def inside package-level func lit + `676) int => `, + `692) elmer => `, // use + + // func garfield(count int) { ... } + `719) garfield => `, + `734) int => `, + `744) calvin => `, // local label def + `811) calvin => `, // label break + }, results) + if len(diff) > 0 { + t.Errorf("FindNestingFunc() mismatch (-want +got):\n%s", diff) + } +} diff --git a/compiler/statements.go b/compiler/statements.go index 17ed8b746..5c7ce8769 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -446,7 +446,7 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { id := spec.(*ast.TypeSpec).Name o := fc.pkgCtx.Defs[id].(*types.TypeName) fc.pkgCtx.typeNames.Add(o) - fc.pkgCtx.DeclareDCEDep(o) + fc.pkgCtx.DeclareDCEDep(o, fc.instance.TArgs, nil) } case token.CONST: // skip, constants are inlined diff --git a/compiler/utils.go b/compiler/utils.go index 7d286f447..34d3f7960 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -415,7 +415,12 @@ func (fc *funcContext) assignedObjectName(o types.Object) (name string, found bo // allocated as needed. func (fc *funcContext) objectName(o types.Object) string { if isPkgLevel(o) { - fc.pkgCtx.DeclareDCEDep(o) + var nestTArgs []types.Type + if typeparams.FindNestingFunc(o) == fc.instance.Object { + // Only set the nest type arguments for objects nested in this funcContext. + nestTArgs = fc.instance.TArgs + } + fc.pkgCtx.DeclareDCEDep(o, nestTArgs, nil) if o.Pkg() != fc.pkgCtx.Pkg || (isVarOrConst(o) && o.Exported()) { return fc.pkgVar(o.Pkg()) + "." + o.Name() @@ -461,7 +466,7 @@ func (fc *funcContext) instName(inst typeparams.Instance) string { if inst.IsTrivial() { return objName } - fc.pkgCtx.DeclareDCEDep(inst.Object, inst.TArgs...) + fc.pkgCtx.DeclareDCEDep(inst.Object, inst.TNest, inst.TArgs) label := inst.TypeParamsString(` /* `, ` */`) return fmt.Sprintf("%s[%d%s]", objName, fc.pkgCtx.instanceSet.ID(inst), label) } @@ -547,7 +552,9 @@ func (fc *funcContext) typeName(ty types.Type) string { fc.pkgCtx.anonTypes = append(fc.pkgCtx.anonTypes, anonType) fc.pkgCtx.anonTypeMap.Set(ty, anonType) } - fc.pkgCtx.DeclareDCEDep(anonType) + // Since anonymous types are always package-level so they can be shared, + // don't pass in the function context (nest type parameters) to the DCE. + fc.pkgCtx.DeclareDCEDep(anonType, nil, nil) return anonType.Name() }