diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 16789971b..fe75880b1 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -68,9 +68,9 @@ func TestDeclSelection_KeepUnusedExportedMethods(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Foo = \$newType`) - sel.DeclCode.IsAlive(`^\s*\$ptrType\(Foo\)\.prototype\.Bar`) - sel.DeclCode.IsAlive(`^\s*\$ptrType\(Foo\)\.prototype\.Baz`) + sel.IsAlive(`type:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo.Bar`) + sel.IsAlive(`func:command-line-arguments.Foo.Baz`) } func TestDeclSelection_RemoveUnusedUnexportedMethods(t *testing.T) { @@ -90,10 +90,10 @@ func TestDeclSelection_RemoveUnusedUnexportedMethods(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Foo = \$newType`) - sel.DeclCode.IsAlive(`^\s*\$ptrType\(Foo\)\.prototype\.Bar`) + sel.IsAlive(`type:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo.Bar`) - sel.DeclCode.IsDead(`^\s*\$ptrType\(Foo\)\.prototype\.baz`) + sel.IsDead(`func:command-line-arguments.Foo.baz`) } func TestDeclSelection_KeepUnusedUnexportedMethodForInterface(t *testing.T) { @@ -126,13 +126,13 @@ func TestDeclSelection_KeepUnusedUnexportedMethodForInterface(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Foo = \$newType`) - sel.DeclCode.IsAlive(`^\s*\$ptrType\(Foo\)\.prototype\.Bar`) + sel.IsAlive(`type:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo.Bar`) // `baz` signature metadata is used to check a type assertion against IFoo, // but the method itself is never called, so it can be removed. - sel.DeclCode.IsDead(`^\s*\$ptrType\(Foo\)\.prototype\.baz`) - sel.MethodListCode.IsAlive(`^\s*Foo.methods = .* \{prop: "baz", name: "baz"`) + // The method is kept in Foo's MethodList for type checking. + sel.IsDead(`func:command-line-arguments.Foo.baz`) } func TestDeclSelection_KeepUnexportedMethodUsedViaInterfaceLit(t *testing.T) { @@ -156,9 +156,9 @@ func TestDeclSelection_KeepUnexportedMethodUsedViaInterfaceLit(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Foo = \$newType`) - sel.DeclCode.IsAlive(`^\s*\$ptrType\(Foo\)\.prototype\.Bar`) - sel.DeclCode.IsAlive(`^\s*\$ptrType\(Foo\)\.prototype\.baz`) + sel.IsAlive(`type:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo.Bar`) + sel.IsAlive(`func:command-line-arguments.Foo.baz`) } func TestDeclSelection_KeepAliveUnexportedMethodsUsedInMethodExpressions(t *testing.T) { @@ -176,8 +176,8 @@ func TestDeclSelection_KeepAliveUnexportedMethodsUsedInMethodExpressions(t *test srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Foo = \$newType`) - sel.DeclCode.IsAlive(`^\s*\$ptrType\(Foo\)\.prototype\.baz`) + sel.IsAlive(`type:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo.baz`) } func TestDeclSelection_RemoveUnusedFuncInstance(t *testing.T) { @@ -200,12 +200,12 @@ func TestDeclSelection_RemoveUnusedFuncInstance(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Sum\[\d+ /\* float64 \*/\]`) - sel.DeclCode.IsAlive(`^\s*sliceType(\$\d+)? = \$sliceType\(\$Float64\)`) + sel.IsAlive(`func:command-line-arguments.Sum`) + sel.IsAlive(`anonType:command-line-arguments.sliceType$1`) // []float64 - sel.DeclCode.IsDead(`^\s*Foo = function`) - sel.DeclCode.IsDead(`^\s*sliceType(\$\d+)? = \$sliceType\(\$Int\)`) - sel.DeclCode.IsDead(`^\s*Sum\[\d+ /\* int \*/\]`) + sel.IsDead(`func:command-line-arguments.Foo`) + sel.IsDead(`anonType:command-line-arguments.sliceType`) // []int + sel.IsDead(`func:command-line-arguments.Sum`) } func TestDeclSelection_RemoveUnusedStructTypeInstances(t *testing.T) { @@ -225,11 +225,11 @@ func TestDeclSelection_RemoveUnusedStructTypeInstances(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Foo\[\d+ /\* int \*/\] = \$newType`) - sel.DeclCode.IsAlive(`^\s*\$ptrType\(Foo\[\d+ /\* int \*/\]\)\.prototype\.Bar`) + sel.IsAlive(`type:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo.Bar`) - sel.DeclCode.IsDead(`^\s*Foo\[\d+ /\* float64 \*/\] = \$newType`) - sel.DeclCode.IsDead(`^\s*\$ptrType\(Foo\[\d+ /\* float64 \*/\]\)\.prototype\.Bar`) + sel.IsDead(`type:command-line-arguments.Foo`) + sel.IsDead(`func:command-line-arguments.Foo.Bar`) } func TestDeclSelection_RemoveUnusedInterfaceTypeInstances(t *testing.T) { @@ -255,18 +255,18 @@ func TestDeclSelection_RemoveUnusedInterfaceTypeInstances(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Baz = \$newType`) - sel.DeclCode.IsAlive(`^\s*Baz\.prototype\.Bar`) - sel.InitCode.IsDead(`\$pkg\.F64 = FooBar\[\d+ /\* float64 \*/\]`) + sel.IsAlive(`type:command-line-arguments.Baz`) + sel.IsAlive(`func:command-line-arguments.Baz.Bar`) + sel.IsDead(`var:command-line-arguments.F64`) - sel.DeclCode.IsAlive(`^\s*FooBar\[\d+ /\* int \*/\]`) + sel.IsAlive(`func:command-line-arguments.FooBar`) // The Foo[int] instance is defined as a parameter in FooBar[int] that is alive. // However, Foo[int] isn't used directly in the code so it can be removed. // JS will simply duck-type the Baz object to Foo[int] without Foo[int] specifically defined. - sel.DeclCode.IsDead(`^\s*Foo\[\d+ /\* int \*/\] = \$newType`) + sel.IsDead(`type:command-line-arguments.Foo`) - sel.DeclCode.IsDead(`^\s*FooBar\[\d+ /\* float64 \*/\]`) - sel.DeclCode.IsDead(`^\s*Foo\[\d+ /\* float64 \*/\] = \$newType`) + sel.IsDead(`func:command-line-arguments.FooBar`) + sel.IsDead(`type:command-line-arguments.Foo`) } func TestDeclSelection_RemoveUnusedMethodWithDifferentSignature(t *testing.T) { @@ -292,13 +292,13 @@ func TestDeclSelection_RemoveUnusedMethodWithDifferentSignature(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Foo = \$newType`) - sel.DeclCode.IsAlive(`\s*\$ptrType\(Foo\)\.prototype\.Bar`) - sel.DeclCode.IsDead(`\s*\$ptrType\(Foo\)\.prototype\.baz`) + sel.IsAlive(`type:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo.Bar`) + sel.IsDead(`func:command-line-arguments.Foo.baz`) - sel.DeclCode.IsAlive(`^\s*Foo2 = \$newType`) - sel.DeclCode.IsAlive(`\s*\$ptrType\(Foo2\)\.prototype\.Bar`) - sel.DeclCode.IsAlive(`\s*\$ptrType\(Foo2\)\.prototype\.baz`) + sel.IsAlive(`type:command-line-arguments.Foo2`) + sel.IsAlive(`func:command-line-arguments.Foo2.Bar`) + sel.IsAlive(`func:command-line-arguments.Foo2.baz`) } func TestDeclSelection_RemoveUnusedUnexportedMethodInstance(t *testing.T) { @@ -323,19 +323,19 @@ func TestDeclSelection_RemoveUnusedUnexportedMethodInstance(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsAlive(`^\s*Foo\[\d+ /\* int \*/\] = \$newType`) - sel.DeclCode.IsAlive(`\s*\$ptrType\(Foo\[\d+ /\* int \*/\]\)\.prototype\.Bar`) - sel.DeclCode.IsAlive(`\s*\$ptrType\(Foo\[\d+ /\* int \*/\]\)\.prototype\.baz`) - sel.DeclCode.IsAlive(`^\s*Baz\[\d+ /\* int \*/\] = \$newType`) - sel.DeclCode.IsAlive(`\s*\$ptrType\(Baz\[\d+ /\* int \*/\]\)\.prototype\.Bar`) + sel.IsAlive(`type:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo.Bar`) + sel.IsAlive(`func:command-line-arguments.Foo.baz`) + sel.IsAlive(`type:command-line-arguments.Baz`) + sel.IsAlive(`func:command-line-arguments.Baz.Bar`) - sel.DeclCode.IsAlive(`^\s*Foo\[\d+ /\* uint \*/\] = \$newType`) - sel.DeclCode.IsAlive(`\s*\$ptrType\(Foo\[\d+ /\* uint \*/\]\)\.prototype\.Bar`) + sel.IsAlive(`type:command-line-arguments.Foo`) + sel.IsAlive(`func:command-line-arguments.Foo.Bar`) // All three below are dead because Foo[uint].baz is unused. - sel.DeclCode.IsDead(`\s*\$ptrType\(Foo\[\d+ /\* uint \*/\]\)\.prototype\.baz`) - sel.DeclCode.IsDead(`^\s*Baz\[\d+ /\* uint \*/\] = \$newType`) - sel.DeclCode.IsDead(`\s*\$ptrType\(Baz\[\d+ /\* uint \*/\]\)\.prototype\.Bar`) + sel.IsDead(`func:command-line-arguments.Foo.baz`) + sel.IsDead(`type:command-line-arguments.Baz`) + sel.IsDead(`func:command-line-arguments.Baz.Bar`) } func TestDeclSelection_RemoveUnusedTypeConstraint(t *testing.T) { @@ -355,10 +355,10 @@ func TestDeclSelection_RemoveUnusedTypeConstraint(t *testing.T) { srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} sel := declSelection(t, srcFiles, nil) - sel.DeclCode.IsDead(`^\s*Foo = \$newType`) - sel.DeclCode.IsDead(`^\s*Bar\[\d+ /\* int \*/\] = \$newType`) - sel.DeclCode.IsDead(`^\s*\$ptrType\(Bar\[\d+ /\* int \*/\]\)\.prototype\.Baz`) - sel.InitCode.IsDead(`ghost = new Bar\[\d+ /\* int \*/\]\.ptr\(7\)`) + sel.IsDead(`type:command-line-arguments.Foo`) + sel.IsDead(`type:command-line-arguments.Bar`) + sel.IsDead(`func:command-line-arguments.Bar.Baz`) + sel.IsDead(`var:command-line-arguments.ghost`) } func TestLengthParenthesizingIssue841(t *testing.T) { @@ -406,6 +406,211 @@ func TestLengthParenthesizingIssue841(t *testing.T) { } } +func TestDeclNaming_Import(t *testing.T) { + src1 := ` + package main + + import ( + newt "github.com/gopherjs/gopherjs/compiler/jorden" + "github.com/gopherjs/gopherjs/compiler/burke" + "github.com/gopherjs/gopherjs/compiler/hudson" + ) + + func main() { + newt.Quote() + burke.Quote() + hudson.Quote() + }` + src2 := `package jorden + func Quote() { println("They mostly come at night... mostly") }` + src3 := `package burke + func Quote() { println("Busy little creatures, huh?") }` + src4 := `package hudson + func Quote() { println("Game over, man! Game over!") }` + + root := srctesting.ParseSources(t, + []srctesting.Source{ + {Name: `main.go`, Contents: []byte(src1)}, + }, + []srctesting.Source{ + {Name: `jorden/rebecca.go`, Contents: []byte(src2)}, + {Name: `burke/carter.go`, Contents: []byte(src3)}, + {Name: `hudson/william.go`, Contents: []byte(src4)}, + }) + + archives := compileProject(t, root, false) + checkForDeclFullNames(t, archives, + `import:github.com/gopherjs/gopherjs/compiler/burke`, + `import:github.com/gopherjs/gopherjs/compiler/hudson`, + `import:github.com/gopherjs/gopherjs/compiler/jorden`, + ) +} + +func TestDeclNaming_FuncAndFuncVar(t *testing.T) { + src := ` + package main + + func Avasarala(value int) { println("Chrisjen", value) } + + func Draper[T any](value T) { println("Bobbie", value) } + + type Nagata struct{ value int } + func (n Nagata) Print() { println("Naomi", n.value) } + + type Burton[T any] struct{ value T } + func (b Burton[T]) Print() { println("Amos", b.value) } + + func main() { + Avasarala(10) + Draper(11) + Draper("Babs") + Nagata{value: 12}.Print() + Burton[int]{value: 13}.Print() + Burton[string]{value: "Timothy"}.Print() + }` + + srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} + root := srctesting.ParseSources(t, srcFiles, nil) + archives := compileProject(t, root, false) + checkForDeclFullNames(t, archives, + `funcVar:command-line-arguments.Avasarala`, + `func:command-line-arguments.Avasarala`, + + `funcVar:command-line-arguments.Draper`, + `func:command-line-arguments.Draper`, + `func:command-line-arguments.Draper`, + + `func:command-line-arguments.Nagata.Print`, + + `typeVar:command-line-arguments.Burton`, + `type:command-line-arguments.Burton`, + `type:command-line-arguments.Burton`, + `func:command-line-arguments.Burton.Print`, + `func:command-line-arguments.Burton.Print`, + + `funcVar:command-line-arguments.main`, + `func:command-line-arguments.main`, + `init:main`, + ) +} + +func TestDeclNaming_InitsAndVars(t *testing.T) { + src1 := ` + package main + + import ( + _ "github.com/gopherjs/gopherjs/compiler/spengler" + _ "github.com/gopherjs/gopherjs/compiler/barrett" + _ "github.com/gopherjs/gopherjs/compiler/tully" + ) + + var peck = "Walter" + func init() { println(peck) } + + func main() { + println("Janosz Poha") + }` + src2 := `package spengler + func init() { println("Egon") } + var egie = func() { println("Dirt Farmer") } + func init() { egie() }` + src3 := `package barrett + func init() { println("Dana") }` + src4 := `package barrett + func init() { println("Zuul") }` + src5 := `package barrett + func init() { println("Gatekeeper") }` + src6 := `package tully + func init() { println("Louis") }` + src7 := `package tully + var keymaster = "Vinz Clortho" + func init() { println(keymaster) }` + + root := srctesting.ParseSources(t, + []srctesting.Source{ + {Name: `main.go`, Contents: []byte(src1)}, + }, + []srctesting.Source{ + {Name: `spengler/a.go`, Contents: []byte(src2)}, + {Name: `barrett/a.go`, Contents: []byte(src3)}, + {Name: `barrett/b.go`, Contents: []byte(src4)}, + {Name: `barrett/c.go`, Contents: []byte(src5)}, + {Name: `tully/a.go`, Contents: []byte(src6)}, + {Name: `tully/b.go`, Contents: []byte(src7)}, + }) + + archives := compileProject(t, root, false) + checkForDeclFullNames(t, archives, + // tully + `var:github.com/gopherjs/gopherjs/compiler/tully.keymaster`, + `funcVar:github.com/gopherjs/gopherjs/compiler/tully.init`, + `funcVar:github.com/gopherjs/gopherjs/compiler/tully.init`, + `func:github.com/gopherjs/gopherjs/compiler/tully.init`, + `func:github.com/gopherjs/gopherjs/compiler/tully.init`, + + // spangler + `var:github.com/gopherjs/gopherjs/compiler/spengler.egie`, + `funcVar:github.com/gopherjs/gopherjs/compiler/spengler.init`, + `funcVar:github.com/gopherjs/gopherjs/compiler/spengler.init`, + `func:github.com/gopherjs/gopherjs/compiler/spengler.init`, + `func:github.com/gopherjs/gopherjs/compiler/spengler.init`, + + // barrett + `funcVar:github.com/gopherjs/gopherjs/compiler/barrett.init`, + `funcVar:github.com/gopherjs/gopherjs/compiler/barrett.init`, + `funcVar:github.com/gopherjs/gopherjs/compiler/barrett.init`, + `func:github.com/gopherjs/gopherjs/compiler/barrett.init`, + `func:github.com/gopherjs/gopherjs/compiler/barrett.init`, + `func:github.com/gopherjs/gopherjs/compiler/barrett.init`, + + // main + `var:command-line-arguments.peck`, + `funcVar:command-line-arguments.init`, + `func:command-line-arguments.init`, + `funcVar:command-line-arguments.main`, + `func:command-line-arguments.main`, + `init:main`, + ) +} + +func TestDeclNaming_VarsAndTypes(t *testing.T) { + src := ` + package main + + var _, shawn, _ = func() (int, string, float64) { + return 1, "Vizzini", 3.14 + }() + + var _ = func() string { + return "Inigo Montoya" + }() + + var fezzik = struct{ value int }{value: 7} + var inigo = struct{ value string }{value: "Montoya"} + + type westley struct{ value string } + + func main() {}` + + srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}} + root := srctesting.ParseSources(t, srcFiles, nil) + + archives := compileProject(t, root, false) + checkForDeclFullNames(t, archives, + `var:command-line-arguments.shawn`, + `var:blank`, + + `var:command-line-arguments.fezzik`, + `anonType:command-line-arguments.structType`, + + `var:command-line-arguments.inigo`, + `anonType:command-line-arguments.structType$1`, + + `typeVar:command-line-arguments.westley`, + `type:command-line-arguments.westley`, + ) +} + func TestArchiveSelectionAfterSerialization(t *testing.T) { src := ` package main @@ -587,10 +792,6 @@ type selectionTester struct { archives map[string]*Archive packages []*Archive dceSelection map[*Decl]struct{} - - DeclCode *selectionCodeTester - InitCode *selectionCodeTester - MethodListCode *selectionCodeTester } func declSelection(t *testing.T, sourceFiles []srctesting.Source, auxFiles []srctesting.Source) *selectionTester { @@ -623,27 +824,6 @@ func declSelection(t *testing.T, sourceFiles []srctesting.Source, auxFiles []src archives: archives, packages: packages, dceSelection: dceSelection, - DeclCode: &selectionCodeTester{ - t: t, - packages: packages, - dceSelection: dceSelection, - codeName: `DeclCode`, - getCode: func(d *Decl) []byte { return d.DeclCode }, - }, - InitCode: &selectionCodeTester{ - t: t, - packages: packages, - dceSelection: dceSelection, - codeName: `InitCode`, - getCode: func(d *Decl) []byte { return d.InitCode }, - }, - MethodListCode: &selectionCodeTester{ - t: t, - packages: packages, - dceSelection: dceSelection, - codeName: `MethodListCode`, - getCode: func(d *Decl) []byte { return d.MethodListCode }, - }, } } @@ -657,65 +837,77 @@ func (st *selectionTester) PrintDeclStatus() { } else { st.t.Logf(` [Dead] %q`, decl.FullName) } - if len(decl.DeclCode) > 0 { - st.t.Logf(` DeclCode: %q`, string(decl.DeclCode)) - } - if len(decl.InitCode) > 0 { - st.t.Logf(` InitCode: %q`, string(decl.InitCode)) - } - if len(decl.MethodListCode) > 0 { - st.t.Logf(` MethodListCode: %q`, string(decl.MethodListCode)) - } - if len(decl.TypeInitCode) > 0 { - st.t.Logf(` TypeInitCode: %q`, string(decl.TypeInitCode)) - } - if len(decl.Vars) > 0 { - st.t.Logf(` Vars: %v`, decl.Vars) - } } } } -type selectionCodeTester struct { - t *testing.T - packages []*Archive - dceSelection map[*Decl]struct{} - codeName string - getCode func(*Decl) []byte -} - -func (ct *selectionCodeTester) IsAlive(pattern string) { - ct.t.Helper() - decl := ct.FindDeclMatch(pattern) - if _, ok := ct.dceSelection[decl]; !ok { - ct.t.Error(`expected the`, ct.codeName, `code to be alive:`, pattern) +func (st *selectionTester) IsAlive(declFullName string) { + st.t.Helper() + decl := st.FindDecl(declFullName) + if _, ok := st.dceSelection[decl]; !ok { + st.t.Error(`expected the decl to be alive:`, declFullName) } } -func (ct *selectionCodeTester) IsDead(pattern string) { - ct.t.Helper() - decl := ct.FindDeclMatch(pattern) - if _, ok := ct.dceSelection[decl]; ok { - ct.t.Error(`expected the`, ct.codeName, `code to be dead:`, pattern) +func (st *selectionTester) IsDead(declFullName string) { + st.t.Helper() + decl := st.FindDecl(declFullName) + if _, ok := st.dceSelection[decl]; ok { + st.t.Error(`expected the decl to be dead:`, declFullName) } } -func (ct *selectionCodeTester) FindDeclMatch(pattern string) *Decl { - ct.t.Helper() - regex := regexp.MustCompile(pattern) +func (st *selectionTester) FindDecl(declFullName string) *Decl { + st.t.Helper() var found *Decl - for _, pkg := range ct.packages { + for _, pkg := range st.packages { for _, d := range pkg.Declarations { - if regex.Match(ct.getCode(d)) { + if d.FullName == declFullName { if found != nil { - ct.t.Fatal(`multiple`, ct.codeName, `found containing pattern:`, pattern) + st.t.Fatal(`multiple decls found with the name`, declFullName) } found = d } } } if found == nil { - ct.t.Fatal(ct.codeName, `not found with pattern:`, pattern) + st.t.Fatal(`no decl found by the name`, declFullName) } return found } + +func checkForDeclFullNames(t *testing.T, archives map[string]*Archive, expectedFullNames ...string) { + t.Helper() + + expected := map[string]int{} + counts := map[string]int{} + for _, name := range expectedFullNames { + expected[name]++ + counts[name]++ + } + for _, pkg := range archives { + for _, decl := range pkg.Declarations { + if found, has := expected[decl.FullName]; has { + if found <= 0 { + t.Errorf(`decl name existed more than %d time(s): %q`, counts[decl.FullName], decl.FullName) + } else { + expected[decl.FullName]-- + } + } + } + } + for imp, found := range expected { + if found > 0 { + t.Errorf(`missing %d decl name(s): %q`, found, imp) + } + } + if t.Failed() { + t.Log("Declarations:") + for pkgName, pkg := range archives { + t.Logf("\t%q", pkgName) + for i, decl := range pkg.Declarations { + t.Logf("\t\t%d:\t%q", i, decl.FullName) + } + } + } +} diff --git a/compiler/declNames.go b/compiler/declNames.go new file mode 100644 index 000000000..4ba59e289 --- /dev/null +++ b/compiler/declNames.go @@ -0,0 +1,70 @@ +package compiler + +import ( + "go/types" + + "github.com/gopherjs/gopherjs/compiler/internal/symbol" + "github.com/gopherjs/gopherjs/compiler/internal/typeparams" +) + +// importDeclFullName returns a unique name for an import declaration. +// This import name may be duplicated in different packages if they both +// import the same package, they are only unique per package. +func importDeclFullName(importedPkg *types.Package) string { + return `import:` + importedPkg.Path() +} + +// varDeclFullName returns a name for a package-level variable declaration. +// This var name only references the first named variable in an assignment. +// If no variables are named, the name is `var:blank` and not unique. +func varDeclFullName(init *types.Initializer) string { + for _, lhs := range init.Lhs { + if lhs.Name() != `_` { + return `var:` + symbol.New(lhs).String() + } + } + return `var:blank` +} + +// funcVarDeclFullName returns a name for a package-level variable +// that is used for a function (without a receiver) declaration. +// The name is unique unless the function is an `init` function. +// If the function is generic, this declaration name is also for the list +// of instantiations of the function. +func funcVarDeclFullName(o *types.Func) string { + return `funcVar:` + symbol.New(o).String() +} + +// mainFuncFullName returns the name for the declaration used to invoke the +// main function of the program. There should only be one decl with this name. +func mainFuncDeclFullName() string { + return `init:main` +} + +// funcDeclFullName returns a name for a package-level function +// declaration for the given instance of a function. +// The name is unique unless the function is an `init` function. +func funcDeclFullName(inst typeparams.Instance) string { + return `func:` + inst.String() +} + +// typeVarDeclFullName returns a unique name for a package-level variable +// that is used for a named 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 { + return `typeVar:` + symbol.New(o).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. +func typeDeclFullName(inst typeparams.Instance) string { + return `type:` + inst.String() +} + +// anonTypeDeclFullName returns a unique name for a package-level type +// declaration for an anonymous type. Names are only unique per package. +// These names are generated for types that are not named in the source code. +func anonTypeDeclFullName(o types.Object) string { + return `anonType:` + symbol.New(o).String() +} diff --git a/compiler/decls.go b/compiler/decls.go index 9f6518875..e292ad07c 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -163,6 +163,7 @@ func (fc *funcContext) importDecls() (importedPaths []string, importDecls []*Dec func (fc *funcContext) newImportDecl(importedPkg *types.Package) *Decl { pkgVar := fc.importedPkgVar(importedPkg) d := &Decl{ + FullName: importDeclFullName(importedPkg), Vars: []string{pkgVar}, DeclCode: []byte(fmt.Sprintf("\t%s = $packages[\"%s\"];\n", pkgVar, importedPkg.Path())), InitCode: fc.CatchOutput(1, func() { fc.translateStmt(fc.importInitializer(importedPkg.Path()), nil) }), @@ -227,7 +228,9 @@ func (fc *funcContext) varDecls(vars []*types.Var) []*Decl { // newVarDecl creates a new Decl describing a variable, given an explicit // initializer. func (fc *funcContext) newVarDecl(init *types.Initializer) *Decl { - var d Decl + d := &Decl{ + FullName: varDeclFullName(init), + } assignLHS := []ast.Expr{} for _, o := range init.Lhs { @@ -244,7 +247,7 @@ func (fc *funcContext) newVarDecl(init *types.Initializer) *Decl { } } - fc.pkgCtx.CollectDCEDeps(&d, func() { + fc.pkgCtx.CollectDCEDeps(d, func() { fc.localVars = nil d.InitCode = fc.CatchOutput(1, func() { fc.translateStmt(&ast.AssignStmt{ @@ -264,7 +267,7 @@ func (fc *funcContext) newVarDecl(init *types.Initializer) *Decl { if len(init.Lhs) != 1 || analysis.HasSideEffect(init.Rhs, fc.pkgCtx.Info.Info) { d.Dce().SetAsAlive() } - return &d + return d } // funcDecls translates all package-level function and methods. @@ -282,15 +285,18 @@ func (fc *funcContext) funcDecls(functions []*ast.FuncDecl) ([]*Decl, error) { if fun.Recv == nil { // Auxiliary decl shared by all instances of the function that defines // package-level variable by which they all are referenced. - varDecl := Decl{} + objName := fc.objectName(o) + varDecl := &Decl{ + FullName: funcVarDeclFullName(o), + Vars: []string{objName}, + } varDecl.Dce().SetName(o) - varDecl.Vars = []string{fc.objectName(o)} if o.Type().(*types.Signature).TypeParams().Len() != 0 { varDecl.DeclCode = fc.CatchOutput(0, func() { - fc.Printf("%s = {};", fc.objectName(o)) + fc.Printf("%s = {};", objName) }) } - funcDecls = append(funcDecls, &varDecl) + funcDecls = append(funcDecls, varDecl) } for _, inst := range fc.knownInstances(o) { @@ -309,6 +315,7 @@ func (fc *funcContext) funcDecls(functions []*ast.FuncDecl) ([]*Decl, error) { // been initialized. It must come after all other functions, especially all // init() functions, otherwise main() will be invoked too early. funcDecls = append(funcDecls, &Decl{ + FullName: mainFuncDeclFullName(), InitCode: fc.CatchOutput(1, func() { fc.translateStmt(fc.callMainFunc(mainFunc), nil) }), }) } @@ -319,7 +326,7 @@ func (fc *funcContext) funcDecls(functions []*ast.FuncDecl) ([]*Decl, error) { func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl, inst typeparams.Instance) *Decl { o := fc.pkgCtx.Defs[fun.Name].(*types.Func) d := &Decl{ - FullName: o.FullName(), + FullName: funcDeclFullName(inst), Blocking: fc.pkgCtx.IsBlocking(inst), LinkingName: symbol.New(o), } @@ -420,15 +427,19 @@ func (fc *funcContext) namedTypeDecls(typeNames typesutil.TypeNames) ([]*Decl, e // of the type, keyed by the type argument combination. Otherwise it contains // the type definition directly. func (fc *funcContext) newNamedTypeVarDecl(obj *types.TypeName) *Decl { - varDecl := &Decl{Vars: []string{fc.objectName(obj)}} + name := fc.objectName(obj) + varDecl := &Decl{ + FullName: typeVarDeclFullName(obj), + Vars: []string{name}, + } if typeparams.HasTypeParams(obj.Type()) { varDecl.DeclCode = fc.CatchOutput(0, func() { - fc.Printf("%s = {};", fc.objectName(obj)) + fc.Printf("%s = {};", name) }) } if isPkgLevel(obj) { varDecl.TypeInitCode = fc.CatchOutput(0, func() { - fc.Printf("$pkg.%s = %s;", encodeIdent(obj.Name()), fc.objectName(obj)) + fc.Printf("$pkg.%s = %s;", encodeIdent(obj.Name()), name) }) } return varDecl @@ -452,7 +463,9 @@ func (fc *funcContext) newNamedTypeInstDecl(inst typeparams.Instance) (*Decl, er } underlying := instanceType.Underlying() - d := &Decl{} + d := &Decl{ + FullName: typeDeclFullName(inst), + } d.Dce().SetName(inst.Object, inst.TArgs...) fc.pkgCtx.CollectDCEDeps(d, func() { // Code that declares a JS type (i.e. prototype) for each Go type. @@ -574,7 +587,8 @@ func (fc *funcContext) anonTypeDecls(anonTypes []*types.TypeName) []*Decl { decls := []*Decl{} for _, t := range anonTypes { d := &Decl{ - Vars: []string{t.Name()}, + FullName: anonTypeDeclFullName(t), + Vars: []string{t.Name()}, } d.Dce().SetName(t) fc.pkgCtx.CollectDCEDeps(d, func() { diff --git a/compiler/internal/analysis/defer.go b/compiler/internal/analysis/defer.go index 65d153d6e..5d4f151a3 100644 --- a/compiler/internal/analysis/defer.go +++ b/compiler/internal/analysis/defer.go @@ -79,7 +79,7 @@ func newLitDefer(lit *ast.FuncLit, typeArgs typesutil.TypeList) *deferStmt { // IsBlocking determines if the defer statement is blocking or not. func (d *deferStmt) IsBlocking(info *Info) bool { - // If the instance or the literal is set then we can look up the blocking, + // If the object or the literal is set then we can look up the blocking, // otherwise assume blocking because otherwise the defer wouldn't // have been recorded. if d.obj != nil { diff --git a/compiler/internal/analysis/info_test.go b/compiler/internal/analysis/info_test.go index 00c1d1623..957e346d9 100644 --- a/compiler/internal/analysis/info_test.go +++ b/compiler/internal/analysis/info_test.go @@ -1691,6 +1691,7 @@ func newBlockingTestWithOtherPackage(t *testing.T, testSrc string, otherSrc stri } func (bt *blockingTest) assertFuncInstCount(expCount int) { + bt.f.T.Helper() if got := bt.pkgInfo.funcInstInfos.Len(); got != expCount { bt.f.T.Errorf(`Got %d function instance infos but expected %d.`, got, expCount) for i, inst := range bt.pkgInfo.funcInstInfos.Keys() { @@ -1700,6 +1701,7 @@ func (bt *blockingTest) assertFuncInstCount(expCount int) { } func (bt *blockingTest) assertFuncLitCount(expCount int) { + bt.f.T.Helper() got := 0 for _, fis := range bt.pkgInfo.funcLitInfos { got += len(fis) @@ -1722,12 +1724,14 @@ func (bt *blockingTest) assertFuncLitCount(expCount int) { } func (bt *blockingTest) assertBlocking(funcName string) { + bt.f.T.Helper() if !bt.isTypesFuncBlocking(funcName) { bt.f.T.Errorf(`Got %q as not blocking but expected it to be blocking.`, funcName) } } func (bt *blockingTest) assertNotBlocking(funcName string) { + bt.f.T.Helper() if bt.isTypesFuncBlocking(funcName) { bt.f.T.Errorf(`Got %q as blocking but expected it to be not blocking.`, funcName) } @@ -1748,6 +1752,7 @@ func getFuncDeclName(fd *ast.FuncDecl) string { } func (bt *blockingTest) isTypesFuncBlocking(funcName string) bool { + bt.f.T.Helper() var decl *ast.FuncDecl ast.Inspect(bt.file, func(n ast.Node) bool { if f, ok := n.(*ast.FuncDecl); ok && getFuncDeclName(f) == funcName { @@ -1771,18 +1776,21 @@ func (bt *blockingTest) isTypesFuncBlocking(funcName string) bool { } func (bt *blockingTest) assertBlockingLit(lineNo int, typeArgsStr string) { + bt.f.T.Helper() if !bt.isFuncLitBlocking(lineNo, typeArgsStr) { bt.f.T.Errorf(`Got FuncLit at line %d with type args %q as not blocking but expected it to be blocking.`, lineNo, typeArgsStr) } } func (bt *blockingTest) assertNotBlockingLit(lineNo int, typeArgsStr string) { + bt.f.T.Helper() if bt.isFuncLitBlocking(lineNo, typeArgsStr) { bt.f.T.Errorf(`Got FuncLit at line %d with type args %q as blocking but expected it to be not blocking.`, lineNo, typeArgsStr) } } func (bt *blockingTest) isFuncLitBlocking(lineNo int, typeArgsStr string) bool { + bt.f.T.Helper() fnLit := srctesting.GetNodeAtLineNo[*ast.FuncLit](bt.file, bt.f.FileSet, lineNo) if fnLit == nil { bt.f.T.Fatalf(`FuncLit on line %d not found in the AST.`, lineNo) @@ -1808,18 +1816,21 @@ func (bt *blockingTest) isFuncLitBlocking(lineNo int, typeArgsStr string) bool { } func (bt *blockingTest) assertBlockingInst(instanceStr string) { + bt.f.T.Helper() if !bt.isFuncInstBlocking(instanceStr) { bt.f.T.Errorf(`Got function instance of %q as not blocking but expected it to be blocking.`, instanceStr) } } func (bt *blockingTest) assertNotBlockingInst(instanceStr string) { + bt.f.T.Helper() if bt.isFuncInstBlocking(instanceStr) { bt.f.T.Errorf(`Got function instance of %q as blocking but expected it to be not blocking.`, instanceStr) } } func (bt *blockingTest) isFuncInstBlocking(instanceStr string) bool { + bt.f.T.Helper() instances := bt.pkgInfo.funcInstInfos.Keys() for _, inst := range instances { if inst.String() == instanceStr { @@ -1828,25 +1839,28 @@ func (bt *blockingTest) isFuncInstBlocking(instanceStr string) bool { } bt.f.T.Logf(`Function instances found in package info:`) for i, inst := range instances { - bt.f.T.Logf(` %d. %s`, i+1, inst.String()) + bt.f.T.Logf("\t%d. %s", i+1, inst.String()) } bt.f.T.Fatalf(`No function instance found for %q in package info.`, instanceStr) return false } func (bt *blockingTest) assertBlockingReturn(lineNo int, typeArgsStr string) { + bt.f.T.Helper() if !bt.isReturnBlocking(lineNo, typeArgsStr) { bt.f.T.Errorf(`Got return at line %d (%q) as not blocking but expected it to be blocking.`, lineNo, typeArgsStr) } } func (bt *blockingTest) assertNotBlockingReturn(lineNo int, typeArgsStr string) { + bt.f.T.Helper() if bt.isReturnBlocking(lineNo, typeArgsStr) { bt.f.T.Errorf(`Got return at line %d (%q) as blocking but expected it to be not blocking.`, lineNo, typeArgsStr) } } func (bt *blockingTest) isReturnBlocking(lineNo int, typeArgsStr string) bool { + bt.f.T.Helper() ret := srctesting.GetNodeAtLineNo[*ast.ReturnStmt](bt.file, bt.f.FileSet, lineNo) if ret == nil { bt.f.T.Fatalf(`ReturnStmt on line %d not found in the AST.`, lineNo) diff --git a/compiler/package.go b/compiler/package.go index 2f6af9c6b..ba018a66b 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -183,12 +183,9 @@ func (ic *ImportContext) isBlocking(inst typeparams.Instance) bool { panic(err) } - // TODO(grantnelson-wf): f.FullName() does not differentiate between - // different instantiations of the same generic function. This needs to be - // fixed when the declaration names are updated to better support instances. - fullName := f.FullName() + fullName := funcDeclFullName(inst) for _, d := range archive.Declarations { - if string(d.FullName) == fullName { + if d.FullName == fullName { return d.Blocking } }