From: Robert Findley Date: Wed, 1 Sep 2021 20:51:17 +0000 (-0400) Subject: go/internal/gcimporter: add import tests for type parameters X-Git-Tag: go1.18beta1~1507 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=4fb79569d2;p=gostls13.git go/internal/gcimporter: add import tests for type parameters Add a new test TestImportTypeparamTests that compiles and imports packages contained in test/typeparam, and compares the resulting package scope with the scope produced by type-checking directly. In the process, fix a bug in go/types affecting embedded instances with more than one type argument. This was uncovered by smoketest.go. To enable this new test it was easiest to move gcimporter_test.go to an external test, which required copying the pkgExts variable. Fixes #48101 Change-Id: Ie4d981bf463e886a8d141809805d184dbbf64607 Reviewed-on: https://go-review.googlesource.com/c/go/+/347070 Trust: Robert Findley Run-TryBot: Robert Findley TryBot-Result: Go Bot Reviewed-by: Robert Griesemer --- diff --git a/src/go/internal/gcimporter/gcimporter_test.go b/src/go/internal/gcimporter/gcimporter_test.go index 3c76aafde3..9f4345d8f9 100644 --- a/src/go/internal/gcimporter/gcimporter_test.go +++ b/src/go/internal/gcimporter/gcimporter_test.go @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package gcimporter +package gcimporter_test import ( "bytes" "fmt" + "internal/goexperiment" "internal/testenv" "os" "os/exec" @@ -16,8 +17,13 @@ import ( "testing" "time" + "go/ast" + "go/importer" + "go/parser" "go/token" "go/types" + + . "go/internal/gcimporter" ) // skipSpecialPlatforms causes the test to be skipped for platforms where @@ -63,6 +69,8 @@ func testPath(t *testing.T, path, srcDir string) *types.Package { const maxTime = 30 * time.Second +var pkgExts = [...]string{".a", ".o"} // keep in sync with gcimporter.go + func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) { dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir) list, err := os.ReadDir(dirname) @@ -134,6 +142,129 @@ func TestImportTestdata(t *testing.T) { } } +func TestImportTypeparamTests(t *testing.T) { + // This test doesn't yet work with the unified export format. + if goexperiment.Unified { + t.Skip("unified export data format is currently unsupported") + } + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + // Check go files in test/typeparam, except those that fail for a known + // reason. + rootDir := filepath.Join(runtime.GOROOT(), "test", "typeparam") + list, err := os.ReadDir(rootDir) + if err != nil { + t.Fatal(err) + } + + skip := map[string]string{ + "equal.go": "inconsistent embedded sorting", // TODO(rfindley): investigate this. + "nested.go": "fails to compile", // TODO(rfindley): investigate this. + } + + for _, entry := range list { + if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") { + // For now, only consider standalone go files. + continue + } + + t.Run(entry.Name(), func(t *testing.T) { + if reason, ok := skip[entry.Name()]; ok { + t.Skip(reason) + } + + filename := filepath.Join(rootDir, entry.Name()) + src, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) { + // We're bypassing the logic of run.go here, so be conservative about + // the files we consider in an attempt to make this test more robust to + // changes in test/typeparams. + t.Skipf("not detected as a run test") + } + + // Compile and import, and compare the resulting package with the package + // that was type-checked directly. + compile(t, rootDir, entry.Name(), filepath.Join(tmpdir, "testdata")) + pkgName := strings.TrimSuffix(entry.Name(), ".go") + imported := importPkg(t, "./testdata/"+pkgName, tmpdir) + checked := checkFile(t, filename, src) + + seen := make(map[string]bool) + for _, name := range imported.Scope().Names() { + if !token.IsExported(name) { + continue // ignore synthetic names like .inittask and .dict.* + } + seen[name] = true + + importedObj := imported.Scope().Lookup(name) + got := types.ObjectString(importedObj, types.RelativeTo(imported)) + got = sanitizeObjectString(got) + + checkedObj := checked.Scope().Lookup(name) + if checkedObj == nil { + t.Fatalf("imported object %q was not type-checked", name) + } + want := types.ObjectString(checkedObj, types.RelativeTo(checked)) + want = sanitizeObjectString(want) + + if got != want { + t.Errorf("imported %q as %q, want %q", name, got, want) + } + } + + for _, name := range checked.Scope().Names() { + if !token.IsExported(name) || seen[name] { + continue + } + t.Errorf("did not import object %q", name) + } + }) + } +} + +// sanitizeObjectString removes type parameter debugging markers from an object +// string, to normalize it for comparison. +// TODO(rfindley): this should not be necessary. +func sanitizeObjectString(s string) string { + var runes []rune + for _, r := range s { + if r == '#' { + continue // trim instance markers + } + if '₀' <= r && r < '₀'+10 { + continue // trim type parameter subscripts + } + runes = append(runes, r) + } + return string(runes) +} + +func checkFile(t *testing.T, filename string, src []byte) *types.Package { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, filename, src, 0) + if err != nil { + t.Fatal(err) + } + config := types.Config{ + Importer: importer.Default(), + } + pkg, err := config.Check("", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + return pkg +} + func TestVersionHandling(t *testing.T) { skipSpecialPlatforms(t) diff --git a/src/go/types/struct.go b/src/go/types/struct.go index 48b07346dc..f6e6f2a5e6 100644 --- a/src/go/types/struct.go +++ b/src/go/types/struct.go @@ -176,6 +176,8 @@ func embeddedFieldIdent(e ast.Expr) *ast.Ident { return e.Sel case *ast.IndexExpr: return embeddedFieldIdent(e.X) + case *ast.MultiIndexExpr: + return embeddedFieldIdent(e.X) } return nil // invalid embedded field }