From 65c5c0bc4162abd81990cc317d570eec75bd274b Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 19 Nov 2024 12:14:44 -0700 Subject: [PATCH 1/4] updating decl naming --- compiler/compiler_test.go | 426 ++++++++++++++++------- compiler/declNames.go | 70 ++++ compiler/decls.go | 40 ++- compiler/functions.go | 2 +- compiler/internal/analysis/defer.go | 21 +- compiler/internal/analysis/info.go | 94 +++-- compiler/internal/analysis/info_test.go | 218 +++++++++--- compiler/internal/typeparams/map.go | 18 +- compiler/internal/typeparams/map_test.go | 2 +- compiler/package.go | 7 +- compiler/typesutil/typelist.go | 12 + 11 files changed, 670 insertions(+), 240 deletions(-) create mode 100644 compiler/declNames.go diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 65178e986..a79e7d4c0 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -67,9 +67,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) { @@ -89,10 +89,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) { @@ -109,7 +109,7 @@ func TestDeclSelection_KeepUnusedUnexportedMethodForInterface(t *testing.T) { println("foo2") } - type IFoo interface { + type IFoo interface { Bar() baz() } @@ -125,13 +125,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) { @@ -155,9 +155,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) { @@ -175,8 +175,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) { @@ -199,12 +199,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) { @@ -224,11 +224,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) { @@ -254,18 +254,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) { @@ -291,13 +291,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) { @@ -322,19 +322,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) { @@ -354,10 +354,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) { @@ -405,6 +405,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 compareOrder(t *testing.T, sourceFiles []srctesting.Source, minify bool) { t.Helper() outputNormal := compile(t, sourceFiles, minify) @@ -503,10 +708,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 { @@ -539,27 +740,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 }, - }, } } @@ -573,65 +753,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..ea4528d9e --- /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 except 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 5419c4c7d..9216f5359 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -160,6 +160,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) }), @@ -224,7 +225,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 { @@ -241,7 +244,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{ @@ -261,7 +264,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. @@ -279,15 +282,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) { @@ -306,6 +312,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) }), }) } @@ -316,7 +323,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), } @@ -417,15 +424,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 @@ -449,7 +460,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. @@ -571,7 +584,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/functions.go b/compiler/functions.go index 86d3916bf..592992efc 100644 --- a/compiler/functions.go +++ b/compiler/functions.go @@ -82,7 +82,7 @@ func (fc *funcContext) namedFuncContext(inst typeparams.Instance) *funcContext { // go/types doesn't generate *types.Func objects for function literals, we // generate a synthetic one for it. func (fc *funcContext) literalFuncContext(fun *ast.FuncLit) *funcContext { - info := fc.pkgCtx.FuncLitInfo(fun) + info := fc.pkgCtx.FuncLitInfo(fun, fc.TypeArgs()) sig := fc.pkgCtx.TypeOf(fun).(*types.Signature) o := types.NewFunc(fun.Pos(), fc.pkgCtx.Pkg, fc.newLitFuncName(), sig) inst := typeparams.Instance{Object: o} diff --git a/compiler/internal/analysis/defer.go b/compiler/internal/analysis/defer.go index 36b19ef2a..5d4f151a3 100644 --- a/compiler/internal/analysis/defer.go +++ b/compiler/internal/analysis/defer.go @@ -2,8 +2,10 @@ package analysis import ( "go/ast" + "go/types" "github.com/gopherjs/gopherjs/compiler/internal/typeparams" + "github.com/gopherjs/gopherjs/compiler/typesutil" ) // deferStmt represents a defer statement that is blocking or not. @@ -49,8 +51,9 @@ import ( // // [CFG]: https://en.wikipedia.org/wiki/Control-flow_graph type deferStmt struct { - inst *typeparams.Instance - lit *ast.FuncLit + obj types.Object + lit *ast.FuncLit + typeArgs typesutil.TypeList } // newBlockingDefer creates a new defer statement that is blocking. @@ -65,25 +68,25 @@ func newBlockingDefer() *deferStmt { // newInstDefer creates a new defer statement for an instances of a method. // The instance is used to look up the blocking information later. func newInstDefer(inst typeparams.Instance) *deferStmt { - return &deferStmt{inst: &inst} + return &deferStmt{obj: inst.Object, typeArgs: inst.TArgs} } // newLitDefer creates a new defer statement for a function literal. // The literal is used to look up the blocking information later. -func newLitDefer(lit *ast.FuncLit) *deferStmt { - return &deferStmt{lit: lit} +func newLitDefer(lit *ast.FuncLit, typeArgs typesutil.TypeList) *deferStmt { + return &deferStmt{lit: lit, typeArgs: typeArgs} } // 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.inst != nil { - return info.FuncInfo(*d.inst).IsBlocking() + if d.obj != nil { + return info.IsBlocking(typeparams.Instance{Object: d.obj, TArgs: d.typeArgs}) } if d.lit != nil { - return info.FuncLitInfo(d.lit).IsBlocking() + return info.FuncLitInfo(d.lit, d.typeArgs).IsBlocking() } return true } diff --git a/compiler/internal/analysis/info.go b/compiler/internal/analysis/info.go index 42918258e..803952b24 100644 --- a/compiler/internal/analysis/info.go +++ b/compiler/internal/analysis/info.go @@ -55,22 +55,24 @@ type Info struct { instanceSets *typeparams.PackageInstanceSets HasPointer map[*types.Var]bool funcInstInfos *typeparams.InstanceMap[*FuncInfo] - funcLitInfos map[*ast.FuncLit]*FuncInfo + funcLitInfos map[*ast.FuncLit][]*FuncInfo InitFuncInfo *FuncInfo // Context for package variable initialization. isImportedBlocking func(typeparams.Instance) bool // For functions from other packages. allInfos []*FuncInfo } -func (info *Info) newFuncInfo(n ast.Node, inst *typeparams.Instance) *FuncInfo { +func (info *Info) newFuncInfo(n ast.Node, obj types.Object, typeArgs typesutil.TypeList, resolver *typeparams.Resolver) *FuncInfo { funcInfo := &FuncInfo{ pkgInfo: info, Flattened: make(map[ast.Node]bool), Blocking: make(map[ast.Node]bool), GotoLabel: make(map[*types.Label]bool), loopReturnIndex: -1, - localInstCallees: new(typeparams.InstanceMap[[]astPath]), + instCallees: new(typeparams.InstanceMap[[]astPath]), literalFuncCallees: make(map[*ast.FuncLit]astPath), + typeArgs: typeArgs, + resolver: resolver, } // Register the function in the appropriate map. @@ -86,13 +88,14 @@ func (info *Info) newFuncInfo(n ast.Node, inst *typeparams.Instance) *FuncInfo { funcInfo.Blocking[n] = true } - if inst == nil { - inst = &typeparams.Instance{Object: info.Defs[n.Name]} + if obj == nil { + obj = info.Defs[n.Name] } - info.funcInstInfos.Set(*inst, funcInfo) + inst := typeparams.Instance{Object: obj, TArgs: typeArgs} + info.funcInstInfos.Set(inst, funcInfo) case *ast.FuncLit: - info.funcLitInfos[n] = funcInfo + info.funcLitInfos[n] = append(info.funcLitInfos[n], funcInfo) } // And add it to the list of all functions. @@ -105,28 +108,40 @@ func (info *Info) newFuncInfoInstances(fd *ast.FuncDecl) []*FuncInfo { obj := info.Defs[fd.Name] instances := info.instanceSets.Pkg(info.Pkg).ForObj(obj) if len(instances) == 0 { - // No instances found, this is a non-generic function. - return []*FuncInfo{info.newFuncInfo(fd, nil)} + if typeparams.HasTypeParams(obj.Type()) { + // This is a generic function, but no instances were found, + // this is an unused function, so skip over it. + return []*FuncInfo{} + } + + // No instances found and this is a non-generic function. + return []*FuncInfo{info.newFuncInfo(fd, nil, nil, nil)} } funcInfos := make([]*FuncInfo, 0, len(instances)) for _, inst := range instances { - fi := info.newFuncInfo(fd, &inst) + var resolver *typeparams.Resolver if sig, ok := obj.Type().(*types.Signature); ok { tp := typeparams.ToSlice(typeparams.SignatureTypeParams(sig)) - fi.resolver = typeparams.NewResolver(info.typeCtx, tp, inst.TArgs) + resolver = typeparams.NewResolver(info.typeCtx, tp, inst.TArgs) } + fi := info.newFuncInfo(fd, inst.Object, inst.TArgs, resolver) funcInfos = append(funcInfos, fi) } return funcInfos } // IsBlocking returns true if the function may contain blocking calls or operations. +// If inst is from a different package, this will use the isImportedBlocking +// to lookup the information from the other package. func (info *Info) IsBlocking(inst typeparams.Instance) bool { + if inst.Object.Pkg() != info.Pkg { + return info.isImportedBlocking(inst) + } if funInfo := info.FuncInfo(inst); funInfo != nil { return funInfo.IsBlocking() } - panic(fmt.Errorf(`info did not have function declaration instance for %q`, inst)) + panic(fmt.Errorf(`info did not have function declaration instance for %q`, inst.TypeString())) } // FuncInfo returns information about the given function declaration instance, or nil if not found. @@ -135,8 +150,16 @@ func (info *Info) FuncInfo(inst typeparams.Instance) *FuncInfo { } // FuncLitInfo returns information about the given function literal, or nil if not found. -func (info *Info) FuncLitInfo(fun *ast.FuncLit) *FuncInfo { - return info.funcLitInfos[fun] +// The given type arguments are used to identify the correct instance of the +// function literal in the case the literal was defined inside a generic function. +func (info *Info) FuncLitInfo(fun *ast.FuncLit, typeArgs typesutil.TypeList) *FuncInfo { + lits := info.funcLitInfos[fun] + for _, fi := range lits { + if fi.typeArgs.Equal(typeArgs) { + return fi + } + } + return nil } // VarsWithInitializers returns a set of package-level variables that have @@ -160,9 +183,9 @@ func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info HasPointer: make(map[*types.Var]bool), isImportedBlocking: isBlocking, funcInstInfos: new(typeparams.InstanceMap[*FuncInfo]), - funcLitInfos: make(map[*ast.FuncLit]*FuncInfo), + funcLitInfos: make(map[*ast.FuncLit][]*FuncInfo), } - info.InitFuncInfo = info.newFuncInfo(nil, nil) + info.InitFuncInfo = info.newFuncInfo(nil, nil, nil, nil) // Traverse the full AST of the package and collect information about existing // functions. @@ -190,19 +213,19 @@ func (info *Info) propagateFunctionBlocking() bool { done := true for _, caller := range info.allInfos { // Check calls to named functions and function-typed variables. - caller.localInstCallees.Iterate(func(callee typeparams.Instance, callSites []astPath) { - if info.FuncInfo(callee).IsBlocking() { + caller.instCallees.Iterate(func(callee typeparams.Instance, callSites []astPath) { + if info.IsBlocking(callee) { for _, callSite := range callSites { caller.markBlocking(callSite) } - caller.localInstCallees.Delete(callee) + caller.instCallees.Delete(callee) done = false } }) // Check direct calls to function literals. for callee, callSite := range caller.literalFuncCallees { - if info.FuncLitInfo(callee).IsBlocking() { + if info.FuncLitInfo(callee, caller.typeArgs).IsBlocking() { caller.markBlocking(callSite) delete(caller.literalFuncCallees, callee) done = false @@ -250,15 +273,18 @@ type FuncInfo struct { // returns defined before any defers in a loop may still be affected by // those defers because of the loop. See comment on [deferStmt]. loopReturnIndex int - // List of other named functions from the current package this function calls. + // List of other named functions in the current package or another package + // that this function calls. // If any of them are blocking, this function will become blocking too. - localInstCallees *typeparams.InstanceMap[[]astPath] + instCallees *typeparams.InstanceMap[[]astPath] // List of function literals directly called from this function (for example: // `func() { /* do stuff */ }()`). This is distinct from function literals // assigned to named variables (for example: `doStuff := func() {}; // doStuff()`), which are handled by localInstCallees. If any of them are // identified as blocking, this function will become blocking too. literalFuncCallees map[*ast.FuncLit]astPath + // typeArgs are the type arguments for the function instance. + typeArgs typesutil.TypeList // resolver is used by this function instance to resolve any type arguments // for internal function calls. // This may be nil if not an instance of a generic function. @@ -276,6 +302,12 @@ func (fi *FuncInfo) IsBlocking() bool { return fi == nil || len(fi.Blocking) != 0 } +// TypeArgs gets the type arguments of this inside of a function instance +// or empty if not in a function instance. +func (fi *FuncInfo) TypeArgs() typesutil.TypeList { + return fi.typeArgs +} + // propagateReturnBlocking updates the blocking on the return statements. // See comment on [deferStmt]. // @@ -341,7 +373,7 @@ func (fi *FuncInfo) Visit(node ast.Node) ast.Visitor { return nil case *ast.FuncLit: // Analyze the function literal in its own context. - return fi.pkgInfo.newFuncInfo(n, nil) + return fi.pkgInfo.newFuncInfo(n, nil, fi.typeArgs, fi.resolver) case *ast.BranchStmt: switch n.Tok { case token.GOTO: @@ -502,7 +534,7 @@ func (fi *FuncInfo) visitCallExpr(n *ast.CallExpr, deferredCall bool) ast.Visito // Register literal function call site in case it is identified as blocking. fi.literalFuncCallees[f] = fi.visitorStack.copy() if deferredCall { - fi.deferStmts = append(fi.deferStmts, newLitDefer(f)) + fi.deferStmts = append(fi.deferStmts, newLitDefer(f, fi.typeArgs)) } return nil // No need to walk under this CallExpr, we already did it manually. case *ast.IndexExpr: @@ -610,21 +642,11 @@ func (fi *FuncInfo) callToNamedFunc(callee typeparams.Instance, deferredCall boo } } - if o.Pkg() != fi.pkgInfo.Pkg { - if fi.pkgInfo.isImportedBlocking(callee) { - fi.markBlocking(fi.visitorStack) - if deferredCall { - fi.deferStmts = append(fi.deferStmts, newBlockingDefer()) - } - } - return - } - // We probably don't know yet whether the callee function is blocking. // Record the calls site for the later stage. - paths := fi.localInstCallees.Get(callee) + paths := fi.instCallees.Get(callee) paths = append(paths, fi.visitorStack.copy()) - fi.localInstCallees.Set(callee, paths) + fi.instCallees.Set(callee, paths) if deferredCall { fi.deferStmts = append(fi.deferStmts, newInstDefer(callee)) } diff --git a/compiler/internal/analysis/info_test.go b/compiler/internal/analysis/info_test.go index d4e21b501..176620d82 100644 --- a/compiler/internal/analysis/info_test.go +++ b/compiler/internal/analysis/info_test.go @@ -156,10 +156,10 @@ func TestBlocking_GoRoutines_WithFuncLiterals(t *testing.T) { }(<-c) }`) bt.assertNotBlocking(`notBlocking`) - bt.assertBlockingLit(4) + bt.assertBlockingLit(4, ``) bt.assertBlocking(`blocking`) - bt.assertNotBlockingLit(10) + bt.assertNotBlockingLit(10, ``) } func TestBlocking_GoRoutines_WithNamedFuncs(t *testing.T) { @@ -210,13 +210,13 @@ func TestBlocking_Defers_WithoutReturns_WithFuncLiterals(t *testing.T) { }(true) }`) bt.assertBlocking(`blockingBody`) - bt.assertBlockingLit(4) + bt.assertBlockingLit(4, ``) bt.assertBlocking(`blockingArg`) - bt.assertNotBlockingLit(10) + bt.assertNotBlockingLit(10, ``) bt.assertNotBlocking(`notBlocking`) - bt.assertNotBlockingLit(16) + bt.assertNotBlockingLit(16, ``) } func TestBlocking_Defers_WithoutReturns_WithNamedFuncs(t *testing.T) { @@ -278,13 +278,13 @@ func TestBlocking_Defers_WithReturns_WithFuncLiterals(t *testing.T) { return 42 }`) bt.assertBlocking(`blockingBody`) - bt.assertBlockingLit(4) + bt.assertBlockingLit(4, ``) bt.assertBlocking(`blockingArg`) - bt.assertNotBlockingLit(11) + bt.assertNotBlockingLit(11, ``) bt.assertNotBlocking(`notBlocking`) - bt.assertNotBlockingLit(18) + bt.assertNotBlockingLit(18, ``) } func TestBlocking_Defers_WithReturns_WithNamedFuncs(t *testing.T) { @@ -363,13 +363,13 @@ func TestBlocking_Defers_WithMultipleReturns(t *testing.T) { return true // line 31 }`) bt.assertBlocking(`foo`) - bt.assertNotBlockingLit(4) + bt.assertNotBlockingLit(4, ``) // Early escape from function without blocking defers is not blocking. bt.assertNotBlockingReturn(11) - bt.assertNotBlockingLit(14) + bt.assertNotBlockingLit(14, ``) // Function has had blocking by this point but no blocking defers yet. bt.assertNotBlockingReturn(20) - bt.assertBlockingLit(24) + bt.assertBlockingLit(24, ``) // The return is blocking because of a blocking defer. bt.assertBlockingReturn(28) // Technically the return on line 31 is not blocking since the defer that @@ -770,13 +770,13 @@ func TestBlocking_FunctionLiteral(t *testing.T) { bt.assertBlocking(`blocking`) bt.assertBlocking(`indirectlyBlocking`) - bt.assertBlockingLit(9) + bt.assertBlockingLit(9, ``) bt.assertBlocking(`directlyBlocking`) - bt.assertBlockingLit(13) + bt.assertBlockingLit(13, ``) bt.assertNotBlocking(`notBlocking`) - bt.assertNotBlockingLit(20) + bt.assertNotBlockingLit(20, ``) } func TestBlocking_LinkedFunction(t *testing.T) { @@ -818,9 +818,9 @@ func TestBlocking_Instances_WithSingleTypeArg(t *testing.T) { // blocking and notBlocking as generics do not have FuncInfo, // only non-generic and instances have FuncInfo. - bt.assertBlockingInst(`test.blocking[int]`) + bt.assertBlockingInst(`pkg/test.blocking`) bt.assertBlocking(`bInt`) - bt.assertNotBlockingInst(`test.notBlocking[uint]`) + bt.assertNotBlockingInst(`pkg/test.notBlocking`) bt.assertNotBlocking(`nbUint`) } @@ -849,9 +849,9 @@ func TestBlocking_Instances_WithMultipleTypeArgs(t *testing.T) { // blocking and notBlocking as generics do not have FuncInfo, // only non-generic and instances have FuncInfo. - bt.assertBlockingInst(`test.blocking[string, int, map[string]int]`) + bt.assertBlockingInst(`pkg/test.blocking`) bt.assertBlocking(`bInt`) - bt.assertNotBlockingInst(`test.notBlocking[string, uint, map[string]uint]`) + bt.assertNotBlockingInst(`pkg/test.notBlocking`) bt.assertNotBlocking(`nbUint`) } @@ -1075,7 +1075,7 @@ func TestBlocking_VarFunctionCall(t *testing.T) { func bar() { foo() }`) - bt.assertNotBlockingLit(3) + bt.assertNotBlockingLit(3, ``) bt.assertBlocking(`bar`) } @@ -1233,12 +1233,12 @@ func TestBlocking_InstantiationBlocking(t *testing.T) { bt.assertBlocking(`BazBlocker.Baz`) bt.assertBlocking(`blockingViaExplicit`) bt.assertBlocking(`blockingViaImplicit`) - bt.assertBlockingInst(`test.FooBaz[pkg/test.BazBlocker]`) + bt.assertBlockingInst(`pkg/test.FooBaz`) bt.assertNotBlocking(`BazNotBlocker.Baz`) bt.assertNotBlocking(`notBlockingViaExplicit`) bt.assertNotBlocking(`notBlockingViaImplicit`) - bt.assertNotBlockingInst(`test.FooBaz[pkg/test.BazNotBlocker]`) + bt.assertNotBlockingInst(`pkg/test.FooBaz`) } func TestBlocking_NestedInstantiations(t *testing.T) { @@ -1272,12 +1272,129 @@ func TestBlocking_NestedInstantiations(t *testing.T) { bt.assertFuncInstCount(8) bt.assertNotBlocking(`bazInt`) bt.assertNotBlocking(`bazString`) - bt.assertNotBlockingInst(`test.Foo[map[int]int]`) - bt.assertNotBlockingInst(`test.Foo[map[int]string]`) - bt.assertNotBlockingInst(`test.Bar[int, int, map[int]int]`) - bt.assertNotBlockingInst(`test.Bar[int, string, map[int]string]`) - bt.assertNotBlockingInst(`test.Baz[int, []int]`) - bt.assertNotBlockingInst(`test.Baz[string, []string]`) + bt.assertNotBlockingInst(`pkg/test.Foo`) + bt.assertNotBlockingInst(`pkg/test.Foo`) + bt.assertNotBlockingInst(`pkg/test.Bar`) + bt.assertNotBlockingInst(`pkg/test.Bar`) + bt.assertNotBlockingInst(`pkg/test.Baz`) + bt.assertNotBlockingInst(`pkg/test.Baz`) +} + +func TestBlocking_UnusedGenericFunctions(t *testing.T) { + // Checking that the type parameters are being propagated down into callee. + // This is based off of go1.19.13/test/typeparam/orderedmap.go + bt := newBlockingTest(t, + `package test + + type node[K, V any] struct { + key K + val V + left, right *node[K, V] + } + + type Tree[K, V any] struct { + root *node[K, V] + eq func(K, K) bool + } + + func New[K, V any](eq func(K, K) bool) *Tree[K, V] { + return &Tree[K, V]{eq: eq} + } + + func NewStrKey[K ~string, V any]() *Tree[K, V] { // unused + return New[K, V](func(k1, k2 K) bool { + return string(k1) == string(k2) + }) + } + + func NewStrStr[V any]() *Tree[string, V] { // unused + return NewStrKey[string, V]() + } + + func main() { + t := New[int, string](func(k1, k2 int) bool { + return k1 == k2 + }) + println(t) + }`) + bt.assertFuncInstCount(2) + // Notice that `NewStrKey` and `NewStrStr` are not called so doesn't have + // any known instances and therefore they don't have any FuncInfos. + bt.assertNotBlockingInst(`pkg/test.New`) + bt.assertNotBlocking(`main`) +} + +func TestBlocking_LitInstanceCalls(t *testing.T) { + // Literals defined inside a generic function must inherit the + // type arguments (resolver) of the enclosing instance it is defined in + // so that things like calls to other generic functions create the + // call to the correct concrete instance. + bt := newBlockingTest(t, + `package test + + func foo[T any](x T) { + println(x) + } + + func bar[T any](x T) { + f := func(v T) { // line 8 + foo[T](v) + } + f(x) + } + + func main() { + bar[int](42) + bar[float64](3.14) + }`) + bt.assertFuncInstCount(5) + + bt.assertNotBlockingInst(`pkg/test.foo`) + bt.assertNotBlockingInst(`pkg/test.foo`) + bt.assertNotBlockingLit(8, `int`) + bt.assertNotBlockingLit(8, `float64`) + // The following are blocking because the function literal call. + bt.assertBlockingInst(`pkg/test.bar`) + bt.assertBlockingInst(`pkg/test.bar`) +} + +func TestBlocking_BlockingLitInstance(t *testing.T) { + bt := newBlockingTest(t, + `package test + + type BazBlocker struct { + c chan bool + } + func (bb BazBlocker) Baz() { + println(<-bb.c) + } + + type BazNotBlocker struct {} + func (bnb BazNotBlocker) Baz() { + println("hi") + } + + type Foo interface { Baz() } + func FooBaz[T Foo](foo T) func() { + return func() { // line 17 + foo.Baz() + } + } + + func main() { + _ = FooBaz(BazBlocker{}) + _ = FooBaz(BazNotBlocker{}) + }`) + bt.assertFuncInstCount(5) + + bt.assertBlocking(`BazBlocker.Baz`) + // THe following is not blocking because the function literal is not called. + bt.assertNotBlockingInst(`pkg/test.FooBaz`) + bt.assertBlockingLit(17, `pkg/test.BazBlocker`) + + bt.assertNotBlocking(`BazNotBlocker.Baz`) + bt.assertNotBlockingInst(`pkg/test.FooBaz`) + bt.assertNotBlockingLit(17, `pkg/test.BazNotBlocker`) } func TestBlocking_MethodSelection(t *testing.T) { @@ -1333,13 +1450,13 @@ func TestBlocking_MethodSelection(t *testing.T) { bt.assertFuncInstCount(8) bt.assertBlocking(`BazBlocker.Baz`) - bt.assertBlockingInst(`test.ByMethodExpression[pkg/test.BazBlocker]`) - bt.assertBlockingInst(`test.ByInstance[pkg/test.BazBlocker]`) + bt.assertBlockingInst(`pkg/test.FooBaz.ByMethodExpression`) + bt.assertBlockingInst(`pkg/test.FooBaz.ByInstance`) bt.assertBlocking(`blocking`) bt.assertNotBlocking(`BazNotBlocker.Baz`) - bt.assertNotBlockingInst(`test.ByMethodExpression[pkg/test.BazNotBlocker]`) - bt.assertNotBlockingInst(`test.ByInstance[pkg/test.BazNotBlocker]`) + bt.assertNotBlockingInst(`pkg/test.FooBaz.ByMethodExpression`) + bt.assertNotBlockingInst(`pkg/test.FooBaz.ByInstance`) bt.assertNotBlocking(`notBlocking`) } @@ -1529,7 +1646,7 @@ func (bt *blockingTest) assertFuncInstCount(expCount int) { 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() { - bt.f.T.Logf(` %d. %q`, i+1, inst.TypeString()) + bt.f.T.Logf(` %d. %q`, i+1, inst.String()) } } } @@ -1597,24 +1714,41 @@ func (bt *blockingTest) isTypesFuncBlocking(funcName string) bool { return bt.pkgInfo.IsBlocking(inst) } -func (bt *blockingTest) assertBlockingLit(lineNo int) { - if !bt.isFuncLitBlocking(lineNo) { - bt.f.T.Errorf(`Got FuncLit at line %d as not blocking but expected it to be blocking.`, lineNo) +func (bt *blockingTest) assertBlockingLit(lineNo int, typeArgsStr string) { + 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) { - if bt.isFuncLitBlocking(lineNo) { - bt.f.T.Errorf(`Got FuncLit at line %d as blocking but expected it to be not blocking.`, lineNo) +func (bt *blockingTest) assertNotBlockingLit(lineNo int, typeArgsStr string) { + 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) bool { +func (bt *blockingTest) isFuncLitBlocking(lineNo int, typeArgsStr string) bool { 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) } - return bt.pkgInfo.FuncLitInfo(fnLit).IsBlocking() + + fis, ok := bt.pkgInfo.funcLitInfos[fnLit] + if !ok { + bt.f.T.Fatalf(`No FuncInfo found for FuncLit at line %d.`, lineNo) + } + + for _, fi := range fis { + if fi.typeArgs.String() == typeArgsStr { + return fi.IsBlocking() + } + } + + bt.f.T.Logf("FuncList instances:") + for i, fi := range fis { + bt.f.T.Logf("\t%d. %q\n", i, fi.typeArgs.String()) + } + bt.f.T.Fatalf(`No FuncInfo found for FuncLit at line %d with type args %q.`, lineNo, typeArgsStr) + return false } func (bt *blockingTest) assertBlockingInst(instanceStr string) { @@ -1632,13 +1766,13 @@ func (bt *blockingTest) assertNotBlockingInst(instanceStr string) { func (bt *blockingTest) isFuncInstBlocking(instanceStr string) bool { instances := bt.pkgInfo.funcInstInfos.Keys() for _, inst := range instances { - if inst.TypeString() == instanceStr { + if inst.String() == instanceStr { return bt.pkgInfo.FuncInfo(inst).IsBlocking() } } bt.f.T.Logf(`Function instances found in package info:`) for i, inst := range instances { - bt.f.T.Logf(` %d. %s`, i+1, inst.TypeString()) + bt.f.T.Logf(` %d. %s`, i+1, inst.String()) } bt.f.T.Fatalf(`No function instance found for %q in package info.`, instanceStr) return false diff --git a/compiler/internal/typeparams/map.go b/compiler/internal/typeparams/map.go index 4f6645421..dbe07a54e 100644 --- a/compiler/internal/typeparams/map.go +++ b/compiler/internal/typeparams/map.go @@ -40,7 +40,7 @@ func (im *InstanceMap[V]) findIndex(key Instance) (mapBucket[V], int) { if im != nil && im.data != nil { bucket := im.data[key.Object][typeHash(im.hasher, key.TArgs...)] for i, candidate := range bucket { - if candidate != nil && typeArgsEq(candidate.key.TArgs, key.TArgs) { + if candidate != nil && candidate.key.TArgs.Equal(key.TArgs) { return bucket, i } } @@ -90,7 +90,7 @@ func (im *InstanceMap[V]) Set(key Instance, value V) V { for i, candidate := range bucket { if candidate == nil { hole = i - } else if typeArgsEq(candidate.key.TArgs, key.TArgs) { + } else if candidate.key.TArgs.Equal(key.TArgs) { old := candidate.value candidate.value = value return old @@ -192,17 +192,3 @@ func typeHash(hasher typeutil.Hasher, types ...types.Type) uint32 { } return hash } - -// typeArgsEq returns if both lists of type arguments are identical. -func typeArgsEq(a, b []types.Type) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if !types.Identical(a[i], b[i]) { - return false - } - } - - return true -} diff --git a/compiler/internal/typeparams/map_test.go b/compiler/internal/typeparams/map_test.go index ed65587c7..baa31f64a 100644 --- a/compiler/internal/typeparams/map_test.go +++ b/compiler/internal/typeparams/map_test.go @@ -317,7 +317,7 @@ func keysMatch(a, b []Instance) bool { func keyAt(keys []Instance, target Instance) int { for i, v := range keys { - if v.Object == target.Object && typeArgsEq(v.TArgs, target.TArgs) { + if v.Object == target.Object && v.TArgs.Equal(target.TArgs) { return i } } diff --git a/compiler/package.go b/compiler/package.go index 4cd800607..95b3f8b9e 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -187,12 +187,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 } } diff --git a/compiler/typesutil/typelist.go b/compiler/typesutil/typelist.go index 04d0d6869..8072b6f44 100644 --- a/compiler/typesutil/typelist.go +++ b/compiler/typesutil/typelist.go @@ -18,3 +18,15 @@ func (tl TypeList) String() string { } return buf.String() } + +func (tl TypeList) Equal(other TypeList) bool { + if len(tl) != len(other) { + return false + } + for i := range tl { + if !types.Identical(tl[i], other[i]) { + return false + } + } + return true +} From ea5dd714a2c7b9e100e00941a619f3258b84f780 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 21 Nov 2024 12:40:44 -0700 Subject: [PATCH 2/4] merged #1351 --- compiler/internal/analysis/info_test.go | 196 ++++++++++++++++-------- compiler/typesutil/typelist.go | 1 + 2 files changed, 136 insertions(+), 61 deletions(-) diff --git a/compiler/internal/analysis/info_test.go b/compiler/internal/analysis/info_test.go index 176620d82..3556324cf 100644 --- a/compiler/internal/analysis/info_test.go +++ b/compiler/internal/analysis/info_test.go @@ -317,15 +317,15 @@ func TestBlocking_Defers_WithReturns_WithNamedFuncs(t *testing.T) { bt.assertNotBlocking(`nonBlockingPrint`) bt.assertBlocking(`blockingBody`) - bt.assertBlockingReturn(13) + bt.assertBlockingReturn(13, ``) bt.assertBlocking(`blockingArg`) // The defer is non-blocking so the return is not blocking // even though the function is blocking. - bt.assertNotBlockingReturn(18) + bt.assertNotBlockingReturn(18, ``) bt.assertNotBlocking(`notBlocking`) - bt.assertNotBlockingReturn(23) + bt.assertNotBlockingReturn(23, ``) } func TestBlocking_Defers_WithMultipleReturns(t *testing.T) { @@ -365,13 +365,13 @@ func TestBlocking_Defers_WithMultipleReturns(t *testing.T) { bt.assertBlocking(`foo`) bt.assertNotBlockingLit(4, ``) // Early escape from function without blocking defers is not blocking. - bt.assertNotBlockingReturn(11) + bt.assertNotBlockingReturn(11, ``) bt.assertNotBlockingLit(14, ``) // Function has had blocking by this point but no blocking defers yet. - bt.assertNotBlockingReturn(20) + bt.assertNotBlockingReturn(20, ``) bt.assertBlockingLit(24, ``) // The return is blocking because of a blocking defer. - bt.assertBlockingReturn(28) + bt.assertBlockingReturn(28, ``) // Technically the return on line 31 is not blocking since the defer that // is blocking can only exit through the return on line 28, but it would be // difficult to determine which defers would only affect certain returns @@ -384,7 +384,7 @@ func TestBlocking_Defers_WithMultipleReturns(t *testing.T) { // // For now we simply build up the list of defers as we go making // the return on line 31 also blocking. - bt.assertBlockingReturn(31) + bt.assertBlockingReturn(31, ``) } func TestBlocking_Defers_WithReturnsAndDefaultBlocking(t *testing.T) { @@ -453,12 +453,12 @@ func TestBlocking_Defers_WithReturnsAndDefaultBlocking(t *testing.T) { bt.assertBlocking(`deferMappedFuncCall`) // All of these returns are blocking because they have blocking defers. - bt.assertBlockingReturn(17) - bt.assertBlockingReturn(22) - bt.assertBlockingReturn(28) - bt.assertBlockingReturn(34) - bt.assertBlockingReturn(40) - bt.assertBlockingReturn(49) + bt.assertBlockingReturn(17, ``) + bt.assertBlockingReturn(22, ``) + bt.assertBlockingReturn(28, ``) + bt.assertBlockingReturn(34, ``) + bt.assertBlockingReturn(40, ``) + bt.assertBlockingReturn(49, ``) } func TestBlocking_Defers_WithReturnsAndDeferBuiltin(t *testing.T) { @@ -477,7 +477,7 @@ func TestBlocking_Defers_WithReturnsAndDeferBuiltin(t *testing.T) { bt.assertFuncInstCount(1) bt.assertNotBlocking(`deferBuiltinCall`) - bt.assertNotBlockingReturn(10) + bt.assertNotBlockingReturn(10, ``) } func TestBlocking_Defers_WithReturnsInLoops(t *testing.T) { @@ -575,14 +575,14 @@ func TestBlocking_Defers_WithReturnsInLoops(t *testing.T) { // When the following 2 returns are defined there are no defers, however, // because of the loop, the blocking defers defined after the return will // block the returns. - bt.assertBlockingReturn(12) - bt.assertBlockingReturn(22) - bt.assertBlockingReturn(31) - bt.assertBlockingReturn(44) - bt.assertBlockingReturn(52) - bt.assertBlockingReturn(66) - bt.assertBlockingReturn(73) - bt.assertBlockingReturn(77) + bt.assertBlockingReturn(12, ``) + bt.assertBlockingReturn(22, ``) + bt.assertBlockingReturn(31, ``) + bt.assertBlockingReturn(44, ``) + bt.assertBlockingReturn(52, ``) + bt.assertBlockingReturn(66, ``) + bt.assertBlockingReturn(73, ``) + bt.assertBlockingReturn(77, ``) } func TestBlocking_Defers_WithReturnsInLoopsInLoops(t *testing.T) { @@ -652,19 +652,19 @@ func TestBlocking_Defers_WithReturnsInLoopsInLoops(t *testing.T) { bt.assertFuncInstCount(4) bt.assertBlocking(`blocking`) bt.assertBlocking(`forLoopTheLoop`) - bt.assertNotBlockingReturn(9) - bt.assertBlockingReturn(13) - bt.assertBlockingReturn(17) - bt.assertBlockingReturn(21) - bt.assertBlockingReturn(25) - bt.assertBlockingReturn(28) + bt.assertNotBlockingReturn(9, ``) + bt.assertBlockingReturn(13, ``) + bt.assertBlockingReturn(17, ``) + bt.assertBlockingReturn(21, ``) + bt.assertBlockingReturn(25, ``) + bt.assertBlockingReturn(28, ``) bt.assertBlocking(`rangeLoopTheLoop`) - bt.assertBlockingReturn(36) - bt.assertBlockingReturn(41) + bt.assertBlockingReturn(36, ``) + bt.assertBlockingReturn(41, ``) bt.assertBlocking(`noopThenLoop`) - bt.assertNotBlockingReturn(48) - bt.assertBlockingReturn(54) - bt.assertBlockingReturn(58) + bt.assertNotBlockingReturn(48, ``) + bt.assertBlockingReturn(54, ``) + bt.assertBlockingReturn(58, ``) } func TestBlocking_Returns_WithoutDefers(t *testing.T) { @@ -693,19 +693,67 @@ func TestBlocking_Returns_WithoutDefers(t *testing.T) { return true // line 22 }`) bt.assertBlocking(`blocking`) - bt.assertBlockingReturn(4) + bt.assertBlockingReturn(4, ``) bt.assertBlocking(`blockingBeforeReturn`) - bt.assertNotBlockingReturn(9) + bt.assertNotBlockingReturn(9, ``) bt.assertBlocking(`indirectlyBlocking`) - bt.assertBlockingReturn(13) + bt.assertBlockingReturn(13, ``) bt.assertBlocking(`indirectlyBlockingBeforeReturn`) - bt.assertNotBlockingReturn(18) + bt.assertNotBlockingReturn(18, ``) bt.assertNotBlocking(`notBlocking`) - bt.assertNotBlockingReturn(22) + bt.assertNotBlockingReturn(22, ``) +} + +func TestBlocking_Defers_WithReturnsInInstances(t *testing.T) { + // This is an example of a deferred function literal inside of + // an instance of a generic function affecting the return + // differently based on the type arguments of the instance. + bt := newBlockingTest(t, + `package test + + type BazBlocker struct { + c chan bool + } + func (bb BazBlocker) Baz() { + println(<-bb.c) + } + + type BazNotBlocker struct {} + func (bnb BazNotBlocker) Baz() { + println("hi") + } + + type Foo interface { Baz() } + func FooBaz[T Foo]() bool { + defer func() { // line 17 + var foo T + foo.Baz() + }() + return true // line 21 + } + + func main() { + FooBaz[BazBlocker]() + FooBaz[BazNotBlocker]() + }`) + + bt.assertFuncInstCount(5) + bt.assertBlocking(`BazBlocker.Baz`) + bt.assertNotBlocking(`BazNotBlocker.Baz`) + bt.assertBlockingInst(`pkg/test.FooBaz`) + bt.assertNotBlockingInst(`pkg/test.FooBaz`) + bt.assertBlocking(`main`) + + bt.assertFuncLitCount(2) + bt.assertBlockingLit(17, `pkg/test.BazBlocker`) + bt.assertNotBlockingLit(17, `pkg/test.BazNotBlocker`) + + bt.assertBlockingReturn(21, `pkg/test.BazBlocker`) + bt.assertNotBlockingReturn(21, `pkg/test.BazNotBlocker`) } func TestBlocking_Defers_WithReturnsAndOtherPackages(t *testing.T) { @@ -737,10 +785,10 @@ func TestBlocking_Defers_WithReturnsAndOtherPackages(t *testing.T) { bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc) bt.assertBlocking(`deferOtherBlocking`) - bt.assertBlockingReturn(7) + bt.assertBlockingReturn(7, ``) bt.assertNotBlocking(`deferOtherNotBlocking`) - bt.assertNotBlockingReturn(12) + bt.assertNotBlockingReturn(12, ``) } func TestBlocking_FunctionLiteral(t *testing.T) { @@ -1652,15 +1700,23 @@ func (bt *blockingTest) assertFuncInstCount(expCount int) { } func (bt *blockingTest) assertFuncLitCount(expCount int) { - if got := len(bt.pkgInfo.funcLitInfos); got != expCount { + got := 0 + for _, fis := range bt.pkgInfo.funcLitInfos { + got += len(fis) + } + if got != expCount { bt.f.T.Errorf(`Got %d function literal infos but expected %d.`, got, expCount) - pos := make([]string, 0, len(bt.pkgInfo.funcLitInfos)) - for fl := range bt.pkgInfo.funcLitInfos { - pos = append(pos, bt.f.FileSet.Position(fl.Pos()).String()) + + lits := make([]string, 0, len(bt.pkgInfo.funcLitInfos)) + for fl, fis := range bt.pkgInfo.funcLitInfos { + pos := bt.f.FileSet.Position(fl.Pos()).String() + for _, fi := range fis { + lits = append(lits, pos+`<`+fi.typeArgs.String()+`>`) + } } - sort.Strings(pos) - for i := range pos { - bt.f.T.Logf(` %d. %q`, i+1, pos) + sort.Strings(lits) + for i := range lits { + bt.f.T.Logf(` %d. %q`, i+1, lits[i]) } } } @@ -1716,13 +1772,13 @@ func (bt *blockingTest) isTypesFuncBlocking(funcName string) bool { func (bt *blockingTest) assertBlockingLit(lineNo int, typeArgsStr string) { 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) + 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) { 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) + bt.f.T.Errorf(`Got FuncLit at line %d with type args %q as blocking but expected it to be not blocking.`, lineNo, typeArgsStr) } } @@ -1745,7 +1801,7 @@ func (bt *blockingTest) isFuncLitBlocking(lineNo int, typeArgsStr string) bool { bt.f.T.Logf("FuncList instances:") for i, fi := range fis { - bt.f.T.Logf("\t%d. %q\n", i, fi.typeArgs.String()) + bt.f.T.Logf("\t%d. %q\n", i+1, fi.typeArgs.String()) } bt.f.T.Fatalf(`No FuncInfo found for FuncLit at line %d with type args %q.`, lineNo, typeArgsStr) return false @@ -1772,34 +1828,52 @@ 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) { - if !bt.isReturnBlocking(lineNo) { - bt.f.T.Errorf(`Got return at line %d as not blocking but expected it to be blocking.`, lineNo) +func (bt *blockingTest) assertBlockingReturn(lineNo int, typeArgsStr string) { + 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) { - if bt.isReturnBlocking(lineNo) { - bt.f.T.Errorf(`Got return at line %d as blocking but expected it to be not blocking.`, lineNo) +func (bt *blockingTest) assertNotBlockingReturn(lineNo int, typeArgsStr string) { + 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) bool { +func (bt *blockingTest) isReturnBlocking(lineNo int, typeArgsStr string) bool { 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) } + + foundInfo := []*FuncInfo{} for _, info := range bt.pkgInfo.allInfos { - if blocking, found := info.Blocking[ret]; found { - return blocking + for _, rs := range info.returnStmts { + if rs.analyzeStack[len(rs.analyzeStack)-1] == ret { + if info.typeArgs.String() == typeArgsStr { + // Found info that matches the type args and + // has the return statement so return the blocking value. + return info.Blocking[ret] + } + + // Wrong instance, record for error message in the case + // that the correct one instance is not found. + foundInfo = append(foundInfo, info) + break + } } } - // If not found in any info.Blocking, then it is not blocking. + + bt.f.T.Logf("FuncInfo instances with ReturnStmt at line %d:", lineNo) + for i, info := range foundInfo { + bt.f.T.Logf("\t%d. %q\n", i+1, info.typeArgs.String()) + } + bt.f.T.Fatalf(`No FuncInfo found for ReturnStmt at line %d with type args %q.`, lineNo, typeArgsStr) return false } diff --git a/compiler/typesutil/typelist.go b/compiler/typesutil/typelist.go index 8072b6f44..768677365 100644 --- a/compiler/typesutil/typelist.go +++ b/compiler/typesutil/typelist.go @@ -19,6 +19,7 @@ func (tl TypeList) String() string { return buf.String() } +// Equal returns true if both lists of type arguments are identical. func (tl TypeList) Equal(other TypeList) bool { if len(tl) != len(other) { return false From 1fa608f5b1f374773e22d94c5d61d349b78137bd Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Mon, 9 Dec 2024 10:51:31 -0700 Subject: [PATCH 3/4] Added Helper calls to info_test --- compiler/internal/analysis/info_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compiler/internal/analysis/info_test.go b/compiler/internal/analysis/info_test.go index 3556324cf..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 { @@ -1835,18 +1846,21 @@ func (bt *blockingTest) isFuncInstBlocking(instanceStr string) bool { } 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) From f0695e070d53a746919b86ab3fd8ea284bc077fe Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 17 Dec 2024 09:52:05 -0700 Subject: [PATCH 4/4] fixing grammar --- compiler/declNames.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/declNames.go b/compiler/declNames.go index ea4528d9e..4ba59e289 100644 --- a/compiler/declNames.go +++ b/compiler/declNames.go @@ -43,7 +43,7 @@ func mainFuncDeclFullName() string { // funcDeclFullName returns a name for a package-level function // declaration for the given instance of a function. -// The name is unique except unless the function is an `init` function. +// The name is unique unless the function is an `init` function. func funcDeclFullName(inst typeparams.Instance) string { return `func:` + inst.String() }