| 1 | // Copyright 2021 The Go Authors. All rights reserved. |
|---|---|
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | //go:build go1.18 |
| 6 | // +build go1.18 |
| 7 | |
| 8 | package gcimporter_test |
| 9 | |
| 10 | import ( |
| 11 | "bytes" |
| 12 | "fmt" |
| 13 | "go/ast" |
| 14 | "go/importer" |
| 15 | "go/parser" |
| 16 | "go/token" |
| 17 | "go/types" |
| 18 | "os" |
| 19 | "path/filepath" |
| 20 | "runtime" |
| 21 | "strings" |
| 22 | "testing" |
| 23 | |
| 24 | "golang.org/x/tools/internal/gcimporter" |
| 25 | "golang.org/x/tools/internal/testenv" |
| 26 | ) |
| 27 | |
| 28 | // TODO(rfindley): migrate this to testdata, as has been done in the standard library. |
| 29 | func TestGenericExport(t *testing.T) { |
| 30 | const src = ` |
| 31 | package generic |
| 32 | |
| 33 | type Any any |
| 34 | |
| 35 | type T[A, B any] struct { Left A; Right B } |
| 36 | |
| 37 | func (T[P, Q]) m() {} |
| 38 | |
| 39 | var X T[int, string] = T[int, string]{1, "hi"} |
| 40 | |
| 41 | func ToInt[P interface{ ~int }](p P) int { return int(p) } |
| 42 | |
| 43 | var IntID = ToInt[int] |
| 44 | |
| 45 | type G[C comparable] int |
| 46 | |
| 47 | func ImplicitFunc[T ~int]() {} |
| 48 | |
| 49 | type ImplicitType[T ~int] int |
| 50 | |
| 51 | // Exercise constant import/export |
| 52 | const C1 = 42 |
| 53 | const C2 int = 42 |
| 54 | const C3 float64 = 42 |
| 55 | |
| 56 | type Constraint[T any] interface { |
| 57 | m(T) |
| 58 | } |
| 59 | |
| 60 | // TODO(rfindley): revert to multiple blanks once the restriction on multiple |
| 61 | // blanks is removed from the type checker. |
| 62 | // type Blanks[_ any, _ Constraint[int]] int |
| 63 | // func (Blanks[_, _]) m() {} |
| 64 | type Blanks[_ any] int |
| 65 | func (Blanks[_]) m() {} |
| 66 | ` |
| 67 | testExportSrc(t, []byte(src)) |
| 68 | } |
| 69 | |
| 70 | func testExportSrc(t *testing.T, src []byte) { |
| 71 | // This package only handles gc export data. |
| 72 | if runtime.Compiler != "gc" { |
| 73 | t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) |
| 74 | } |
| 75 | |
| 76 | fset := token.NewFileSet() |
| 77 | f, err := parser.ParseFile(fset, "g.go", src, 0) |
| 78 | if err != nil { |
| 79 | t.Fatal(err) |
| 80 | } |
| 81 | conf := types.Config{ |
| 82 | Importer: importer.Default(), |
| 83 | } |
| 84 | pkg, err := conf.Check("", fset, []*ast.File{f}, nil) |
| 85 | if err != nil { |
| 86 | t.Fatal(err) |
| 87 | } |
| 88 | |
| 89 | // export |
| 90 | version := gcimporter.IExportVersion |
| 91 | data, err := iexport(fset, version, pkg) |
| 92 | if err != nil { |
| 93 | t.Fatal(err) |
| 94 | } |
| 95 | |
| 96 | testPkgData(t, fset, version, pkg, data) |
| 97 | } |
| 98 | |
| 99 | func TestImportTypeparamTests(t *testing.T) { |
| 100 | testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache |
| 101 | |
| 102 | // Check go files in test/typeparam. |
| 103 | rootDir := filepath.Join(runtime.GOROOT(), "test", "typeparam") |
| 104 | list, err := os.ReadDir(rootDir) |
| 105 | if err != nil { |
| 106 | t.Fatal(err) |
| 107 | } |
| 108 | |
| 109 | if isUnifiedBuilder() { |
| 110 | t.Skip("unified export data format is currently unsupported") |
| 111 | } |
| 112 | |
| 113 | for _, entry := range list { |
| 114 | if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") { |
| 115 | // For now, only consider standalone go files. |
| 116 | continue |
| 117 | } |
| 118 | |
| 119 | t.Run(entry.Name(), func(t *testing.T) { |
| 120 | filename := filepath.Join(rootDir, entry.Name()) |
| 121 | src, err := os.ReadFile(filename) |
| 122 | if err != nil { |
| 123 | t.Fatal(err) |
| 124 | } |
| 125 | |
| 126 | if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) { |
| 127 | // We're bypassing the logic of run.go here, so be conservative about |
| 128 | // the files we consider in an attempt to make this test more robust to |
| 129 | // changes in test/typeparams. |
| 130 | t.Skipf("not detected as a run test") |
| 131 | } |
| 132 | |
| 133 | testExportSrc(t, src) |
| 134 | }) |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | func TestRecursiveExport_Issue51219(t *testing.T) { |
| 139 | const srca = ` |
| 140 | package a |
| 141 | |
| 142 | type Interaction[DataT InteractionDataConstraint] struct { |
| 143 | } |
| 144 | |
| 145 | type InteractionDataConstraint interface { |
| 146 | []byte | |
| 147 | UserCommandInteractionData |
| 148 | } |
| 149 | |
| 150 | type UserCommandInteractionData struct { |
| 151 | resolvedInteractionWithOptions |
| 152 | } |
| 153 | |
| 154 | type resolvedInteractionWithOptions struct { |
| 155 | Resolved Resolved |
| 156 | } |
| 157 | |
| 158 | type Resolved struct { |
| 159 | Users ResolvedData[User] |
| 160 | } |
| 161 | |
| 162 | type ResolvedData[T ResolvedDataConstraint] map[uint64]T |
| 163 | |
| 164 | type ResolvedDataConstraint interface { |
| 165 | User | Message |
| 166 | } |
| 167 | |
| 168 | type User struct{} |
| 169 | |
| 170 | type Message struct { |
| 171 | Interaction *Interaction[[]byte] |
| 172 | } |
| 173 | ` |
| 174 | |
| 175 | const srcb = ` |
| 176 | package b |
| 177 | |
| 178 | import ( |
| 179 | "a" |
| 180 | ) |
| 181 | |
| 182 | // InteractionRequest is an incoming request Interaction |
| 183 | type InteractionRequest[T a.InteractionDataConstraint] struct { |
| 184 | a.Interaction[T] |
| 185 | } |
| 186 | ` |
| 187 | |
| 188 | const srcp = ` |
| 189 | package p |
| 190 | |
| 191 | import ( |
| 192 | "b" |
| 193 | ) |
| 194 | |
| 195 | // ResponseWriterMock mocks corde's ResponseWriter interface |
| 196 | type ResponseWriterMock struct { |
| 197 | x b.InteractionRequest[[]byte] |
| 198 | } |
| 199 | ` |
| 200 | |
| 201 | importer := &testImporter{ |
| 202 | src: map[string][]byte{ |
| 203 | "a": []byte(srca), |
| 204 | "b": []byte(srcb), |
| 205 | "p": []byte(srcp), |
| 206 | }, |
| 207 | pkgs: make(map[string]*types.Package), |
| 208 | } |
| 209 | _, err := importer.Import("p") |
| 210 | if err != nil { |
| 211 | t.Fatal(err) |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | // testImporter is a helper to test chains of imports using export data. |
| 216 | type testImporter struct { |
| 217 | src map[string][]byte // original source |
| 218 | pkgs map[string]*types.Package // memoized imported packages |
| 219 | } |
| 220 | |
| 221 | func (t *testImporter) Import(path string) (*types.Package, error) { |
| 222 | if pkg, ok := t.pkgs[path]; ok { |
| 223 | return pkg, nil |
| 224 | } |
| 225 | src, ok := t.src[path] |
| 226 | if !ok { |
| 227 | return nil, fmt.Errorf("unknown path %v", path) |
| 228 | } |
| 229 | |
| 230 | // Type-check, but don't return this package directly. |
| 231 | fset := token.NewFileSet() |
| 232 | f, err := parser.ParseFile(fset, path+".go", src, 0) |
| 233 | if err != nil { |
| 234 | return nil, err |
| 235 | } |
| 236 | conf := types.Config{ |
| 237 | Importer: t, |
| 238 | } |
| 239 | pkg, err := conf.Check(path, fset, []*ast.File{f}, nil) |
| 240 | if err != nil { |
| 241 | return nil, err |
| 242 | } |
| 243 | |
| 244 | // Export and import to get the package imported from export data. |
| 245 | exportdata, err := iexport(fset, gcimporter.IExportVersion, pkg) |
| 246 | if err != nil { |
| 247 | return nil, err |
| 248 | } |
| 249 | imports := make(map[string]*types.Package) |
| 250 | fset2 := token.NewFileSet() |
| 251 | _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) |
| 252 | if err != nil { |
| 253 | return nil, err |
| 254 | } |
| 255 | t.pkgs[path] = pkg2 |
| 256 | return pkg2, nil |
| 257 | } |
| 258 |
Members