// Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package gcimporter_test // This file defines test of generics features introduce in go1.18. import ( "bytes" "fmt" "go/ast" "go/importer" "go/parser" "go/token" "go/types" "os" "path/filepath" "runtime" "strings" "testing" "golang.org/x/tools/internal/gcimporter" "golang.org/x/tools/internal/testenv" ) // TODO(rfindley): migrate this to testdata, as has been done in the standard library. func TestGenericExport(t *testing.T) { const src = ` package generic type Any any type T[A, B any] struct { Left A; Right B } func (T[P, Q]) m() {} var X T[int, string] = T[int, string]{1, "hi"} func ToInt[P interface{ ~int }](p P) int { return int(p) } var IntID = ToInt[int] type G[C comparable] int func ImplicitFunc[T ~int]() {} type ImplicitType[T ~int] int // Exercise constant import/export const C1 = 42 const C2 int = 42 const C3 float64 = 42 type Constraint[T any] interface { m(T) } // TODO(rfindley): revert to multiple blanks once the restriction on multiple // blanks is removed from the type checker. // type Blanks[_ any, _ Constraint[int]] int // func (Blanks[_, _]) m() {} type Blanks[_ any] int func (Blanks[_]) m() {} ` testExportSrc(t, []byte(src)) } func testExportSrc(t *testing.T, src []byte) { // This package only handles gc export data. if runtime.Compiler != "gc" { t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) } testenv.NeedsGoBuild(t) fset := token.NewFileSet() f, err := parser.ParseFile(fset, "g.go", src, 0) if err != nil { t.Fatal(err) } conf := types.Config{ Importer: importer.Default(), } pkg, err := conf.Check("", fset, []*ast.File{f}, nil) if err != nil { t.Fatal(err) } // export version := gcimporter.IExportVersion data, err := iexport(fset, version, pkg) if err != nil { t.Fatal(err) } testPkgData(t, fset, version, pkg, data) } func TestImportTypeparamTests(t *testing.T) { testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache // Check go files in test/typeparam. rootDir := filepath.Join(runtime.GOROOT(), "test", "typeparam") list, err := os.ReadDir(rootDir) if err != nil { t.Fatal(err) } if isUnifiedBuilder() { t.Skip("unified export data format is currently unsupported") } 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) { 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") } testExportSrc(t, src) }) } } func TestRecursiveExport_Issue51219(t *testing.T) { const srca = ` package a type Interaction[DataT InteractionDataConstraint] struct { } type InteractionDataConstraint interface { []byte | UserCommandInteractionData } type UserCommandInteractionData struct { resolvedInteractionWithOptions } type resolvedInteractionWithOptions struct { Resolved Resolved } type Resolved struct { Users ResolvedData[User] } type ResolvedData[T ResolvedDataConstraint] map[uint64]T type ResolvedDataConstraint interface { User | Message } type User struct{} type Message struct { Interaction *Interaction[[]byte] } ` const srcb = ` package b import ( "a" ) // InteractionRequest is an incoming request Interaction type InteractionRequest[T a.InteractionDataConstraint] struct { a.Interaction[T] } ` const srcp = ` package p import ( "b" ) // ResponseWriterMock mocks corde's ResponseWriter interface type ResponseWriterMock struct { x b.InteractionRequest[[]byte] } ` importer := &testImporter{ src: map[string][]byte{ "a": []byte(srca), "b": []byte(srcb), "p": []byte(srcp), }, pkgs: make(map[string]*types.Package), } _, err := importer.Import("p") if err != nil { t.Fatal(err) } } // testImporter is a helper to test chains of imports using export data. type testImporter struct { src map[string][]byte // original source pkgs map[string]*types.Package // memoized imported packages } func (t *testImporter) Import(path string) (*types.Package, error) { if pkg, ok := t.pkgs[path]; ok { return pkg, nil } src, ok := t.src[path] if !ok { return nil, fmt.Errorf("unknown path %v", path) } // Type-check, but don't return this package directly. fset := token.NewFileSet() f, err := parser.ParseFile(fset, path+".go", src, 0) if err != nil { return nil, err } conf := types.Config{ Importer: t, } pkg, err := conf.Check(path, fset, []*ast.File{f}, nil) if err != nil { return nil, err } // Export and import to get the package imported from export data. exportdata, err := iexport(fset, gcimporter.IExportVersion, pkg) if err != nil { return nil, err } imports := make(map[string]*types.Package) fset2 := token.NewFileSet() _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) if err != nil { return nil, err } t.pkgs[path] = pkg2 return pkg2, nil }