diff --git a/compiler/expressions.go b/compiler/expressions.go index dcf1b7844..dce28183a 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -620,6 +620,10 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { if typesutil.IsJsPackage(obj.Pkg()) && obj.Name() == "InternalObject" { return fc.translateExpr(e.Args[0]) } + + if inst, has := fc.pkgCtx.Instances[f]; has { + fc.pkgCtx.DeclareDCEDepWithInstance(obj, inst) + } return fc.translateCall(e, sig, fc.translateExpr(f)) case *ast.SelectorExpr: diff --git a/compiler/internal/dce/collector.go b/compiler/internal/dce/collector.go index 7d251029b..b5e0986bb 100644 --- a/compiler/internal/dce/collector.go +++ b/compiler/internal/dce/collector.go @@ -3,6 +3,9 @@ package dce import ( "errors" "go/types" + "strings" + + "github.com/gopherjs/gopherjs/compiler/typesutil" ) // Decl is any code declaration that has dead-code elimination (DCE) @@ -14,7 +17,7 @@ type Decl interface { // Collector is a tool to collect dependencies for a declaration // that'll be used in dead-code elimination (DCE). type Collector struct { - dependencies map[types.Object]struct{} + dependencies map[string]struct{} } // CollectDCEDeps captures a list of Go objects (types, functions, etc.) @@ -28,7 +31,7 @@ func (c *Collector) CollectDCEDeps(decl Decl, f func()) { panic(errors.New(`called CollectDCEDeps inside another CollectDCEDeps call`)) } - c.dependencies = make(map[types.Object]struct{}) + c.dependencies = make(map[string]struct{}) defer func() { c.dependencies = nil }() f() @@ -39,8 +42,42 @@ func (c *Collector) CollectDCEDeps(decl Decl, f func()) { // DeclareDCEDep records that the code that is currently being transpiled // depends on a given Go object. func (c *Collector) DeclareDCEDep(o types.Object) { + c.DeclareDCEDepWithInstance(o, types.Instance{}) +} + +func (c *Collector) DeclareDCEDepWithInstance(o types.Object, inst types.Instance) { if c.dependencies == nil { return // Dependencies are not being collected. } - c.dependencies[o] = struct{}{} + + // add object dependency + // If the object is a type it will be added as . (e.g. "foo.Bar") + // If the object is a method (with a receiver) have a tilde added (e.g. "foo.Bar~") + // If the object is an instance of a generic, the type arguments are + // added (e.g. "foo.Bar[int, string]" or "foo.Bar[int, string]~"). + // + // The methods are set as dependencies without their receiver type since + // the DCE doesn't track if the method is part of an interface or a concrete type. + // This means that if a method is used, all types that have this method will be kept + // since the method is not tied to a specific type and maybe used via an interface. + qualifiedName := o.Pkg().Path() + "." + o.Name() + if inst.TypeArgs != nil { + tps := make([]string, inst.TypeArgs.Len()) + for i := range tps { + tps[i] = inst.TypeArgs.At(i).String() + } + qualifiedName += `[` + strings.Join(tps, `, `) + `]` + } + if typesutil.IsMethod(o) { + qualifiedName += "~" + } + c.dependencies[qualifiedName] = struct{}{} + + // TODO(gn): Things to test: + // - Casting to type (e.g. `type X[T any] interface{ Get() T }; struct Y{}; func (y Y) Get() int { return 42 }; var v X[int] = Y{}`) + // - Complex type parameters (e.g. `type X[T any] struct{v T}; func (x X[T]) Get() T { return v }; var v = X[X[X[int]]]{v: X[X[int]]{v: X[int]{v: 42}}}.Get().Get().Get()`) + // - Automatic type selection (e.g. Sum[T int | float64](values ...T) T { ** }; var vi = Sum(42, 43), vf = Sum(3.14, 4.25)`) + // - Embedded instance in type (e.g. `type X[T any] struct{v T}; type Y[T any] struct{X[T]}; var v = Y[int]{X: X[int]{v: 42}}.X.v`) + // - Embedded interface in struct providing the type with an exposed method. + // - Type params that are self referencing (e.g. `func Keys[K comparable, V any, M ~map[K]V](m M) { ** }`) } diff --git a/compiler/internal/dce/dce_test.go b/compiler/internal/dce/dce_test.go index c46a7f03c..3ce93c360 100644 --- a/compiler/internal/dce/dce_test.go +++ b/compiler/internal/dce/dce_test.go @@ -238,8 +238,9 @@ func Test_Info_SetNameAndDep(t *testing.T) { d := &testDecl{} t.Log(`object:`, types.ObjectString(tt.obj, nil)) - d.Dce().setDeps(map[types.Object]struct{}{ - tt.obj: {}, + c := Collector{} + c.CollectDCEDeps(d, func() { + c.DeclareDCEDep(tt.obj) }) equal(t, len(d.Dce().deps), 1) equal(t, d.Dce().deps[0], tt.wantDep) diff --git a/compiler/internal/dce/info.go b/compiler/internal/dce/info.go index d5993a659..95fbfb5a8 100644 --- a/compiler/internal/dce/info.go +++ b/compiler/internal/dce/info.go @@ -94,15 +94,16 @@ func (d *Info) SetName(o types.Object) { // setDeps sets the declaration dependencies used by DCE // for the declaration this DCE info is attached to. // This overwrites any prior set dependencies. -func (d *Info) setDeps(objectSet map[types.Object]struct{}) { - deps := make([]string, 0, len(objectSet)) - for o := range objectSet { - qualifiedName := o.Pkg().Path() + "." + o.Name() - if typesutil.IsMethod(o) { - qualifiedName += "~" - } - deps = append(deps, qualifiedName) +func (d *Info) setDeps(depSet map[string]struct{}) { + deps := make([]string, 0, len(depSet)) + for dep := range depSet { + deps = append(deps, dep) } sort.Strings(deps) d.deps = deps + + // TODO(gn): REMOVE + if d.importPath == `main` { + fmt.Printf(">setDeps: %s\n", d.String()) + } } diff --git a/compiler/statements.go b/compiler/statements.go index d4ca76471..937a25f2d 100644 --- a/compiler/statements.go +++ b/compiler/statements.go @@ -443,9 +443,14 @@ func (fc *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { } case token.TYPE: for _, spec := range decl.Specs { - o := fc.pkgCtx.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) + id := spec.(*ast.TypeSpec).Name + o := fc.pkgCtx.Defs[id].(*types.TypeName) fc.pkgCtx.typeNames.Add(o) - fc.pkgCtx.DeclareDCEDep(o) + if inst, has := fc.pkgCtx.Instances[id]; has { + fc.pkgCtx.DeclareDCEDepWithInstance(o, inst) + } else { + fc.pkgCtx.DeclareDCEDep(o) + } } case token.CONST: // skip, constants are inlined diff --git a/compiler/utils.go b/compiler/utils.go index a69d0fe77..c878ba8e9 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -514,7 +514,7 @@ func (fc *funcContext) typeName(ty types.Type) string { } // For anonymous composite types, generate a synthetic package-level type - // declaration, which will be reused for all instances of this time. This + // declaration, which will be reused for all instances of this type. This // improves performance, since runtime won't have to synthesize the same type // repeatedly. anonType, ok := fc.pkgCtx.anonTypeMap.At(ty).(*types.TypeName)