Source file
src/go/types/stdlib_test.go
1
2
3
4
5
6
7
8 package types_test
9
10 import (
11 "errors"
12 "fmt"
13 "go/ast"
14 "go/build"
15 "go/importer"
16 "go/parser"
17 "go/scanner"
18 "go/token"
19 "internal/testenv"
20 "os"
21 "path/filepath"
22 "runtime"
23 "slices"
24 "strings"
25 "sync"
26 "testing"
27 "time"
28
29 . "go/types"
30 )
31
32
33
34
35
36
37
38
39
40 var stdLibImporter = importer.ForCompiler(token.NewFileSet(), "source", nil)
41
42 func TestStdlib(t *testing.T) {
43 if testing.Short() {
44 t.Skip("skipping in short mode")
45 }
46
47 testenv.MustHaveGoBuild(t)
48
49
50 dirFiles := make(map[string][]string)
51 root := filepath.Join(testenv.GOROOT(t), "src")
52 walkPkgDirs(root, func(dir string, filenames []string) {
53 dirFiles[dir] = filenames
54 }, t.Error)
55
56 c := &stdlibChecker{
57 dirFiles: dirFiles,
58 pkgs: make(map[string]*futurePackage),
59 }
60
61 start := time.Now()
62
63
64
65
66
67
68
69 cpulimit := make(chan struct{}, runtime.GOMAXPROCS(0))
70 var wg sync.WaitGroup
71
72 for dir := range dirFiles {
73 dir := dir
74
75 cpulimit <- struct{}{}
76 wg.Add(1)
77 go func() {
78 defer func() {
79 wg.Done()
80 <-cpulimit
81 }()
82
83 _, err := c.getDirPackage(dir)
84 if err != nil {
85 t.Errorf("error checking %s: %v", dir, err)
86 }
87 }()
88 }
89
90 wg.Wait()
91
92 if testing.Verbose() {
93 fmt.Println(len(dirFiles), "packages typechecked in", time.Since(start))
94 }
95 }
96
97
98
99 type stdlibChecker struct {
100 dirFiles map[string][]string
101
102 mu sync.Mutex
103 pkgs map[string]*futurePackage
104 }
105
106
107 type futurePackage struct {
108 done chan struct{}
109 pkg *Package
110 err error
111 }
112
113 func (c *stdlibChecker) Import(path string) (*Package, error) {
114 panic("unimplemented: use ImportFrom")
115 }
116
117 func (c *stdlibChecker) ImportFrom(path, dir string, _ ImportMode) (*Package, error) {
118 if path == "unsafe" {
119
120 return Unsafe, nil
121 }
122
123 p, err := build.Default.Import(path, dir, build.FindOnly)
124 if err != nil {
125 return nil, err
126 }
127
128 pkg, err := c.getDirPackage(p.Dir)
129 if pkg != nil {
130
131
132 return pkg, nil
133 }
134 return nil, err
135 }
136
137
138
139
140
141 func (c *stdlibChecker) getDirPackage(dir string) (*Package, error) {
142 c.mu.Lock()
143 fut, ok := c.pkgs[dir]
144 if !ok {
145
146 fut = &futurePackage{
147 done: make(chan struct{}),
148 }
149 c.pkgs[dir] = fut
150 files, ok := c.dirFiles[dir]
151 c.mu.Unlock()
152 if !ok {
153 fut.err = fmt.Errorf("no files for %s", dir)
154 } else {
155
156
157
158 fut.pkg, fut.err = typecheckFiles(dir, files, c)
159 }
160 close(fut.done)
161 } else {
162
163 c.mu.Unlock()
164 <-fut.done
165 }
166 return fut.pkg, fut.err
167 }
168
169
170
171
172
173
174 func firstComment(filename string) string {
175 f, err := os.Open(filename)
176 if err != nil {
177 return ""
178 }
179 defer f.Close()
180
181 var src [4 << 10]byte
182 n, _ := f.Read(src[:])
183
184 var first string
185 var s scanner.Scanner
186 s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil , scanner.ScanComments)
187 for {
188 _, tok, lit := s.Scan()
189 switch tok {
190 case token.COMMENT:
191
192 if lit[1] == '*' {
193 lit = lit[:len(lit)-2]
194 }
195 contents := strings.TrimSpace(lit[2:])
196 if strings.HasPrefix(contents, "go:build ") {
197 return "skip"
198 }
199 if first == "" {
200 first = contents
201 }
202
203
204 case token.PACKAGE, token.EOF:
205 return first
206 }
207 }
208 }
209
210 func testTestDir(t *testing.T, path string, ignore ...string) {
211 files, err := os.ReadDir(path)
212 if err != nil {
213
214
215
216 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "test")); os.IsNotExist(err) {
217 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
218 t.Skipf("skipping: GOROOT/test not present")
219 }
220 }
221 t.Fatal(err)
222 }
223
224 excluded := make(map[string]bool)
225 for _, filename := range ignore {
226 excluded[filename] = true
227 }
228
229 fset := token.NewFileSet()
230 for _, f := range files {
231
232 if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
233 continue
234 }
235
236
237 expectErrors := false
238 filename := filepath.Join(path, f.Name())
239 goVersion := ""
240 if comment := firstComment(filename); comment != "" {
241 if strings.Contains(comment, "-goexperiment") {
242 continue
243 }
244 fields := strings.Fields(comment)
245 switch fields[0] {
246 case "skip", "compiledir":
247 continue
248 case "errorcheck":
249 expectErrors = true
250 for _, arg := range fields[1:] {
251 if arg == "-0" || arg == "-+" || arg == "-std" {
252
253
254
255
256 expectErrors = false
257 break
258 }
259 const prefix = "-lang="
260 if strings.HasPrefix(arg, prefix) {
261 goVersion = arg[len(prefix):]
262 }
263 }
264 }
265 }
266
267
268 file, err := parser.ParseFile(fset, filename, nil, 0)
269 if err == nil {
270 conf := Config{
271 GoVersion: goVersion,
272 Importer: stdLibImporter,
273 }
274 _, err = conf.Check(filename, fset, []*ast.File{file}, nil)
275 }
276
277 if expectErrors {
278 if err == nil {
279 t.Errorf("expected errors but found none in %s", filename)
280 }
281 } else {
282 if err != nil {
283 t.Error(err)
284 }
285 }
286 }
287 }
288
289 func TestStdTest(t *testing.T) {
290 testenv.MustHaveGoBuild(t)
291
292 if testing.Short() && testenv.Builder() == "" {
293 t.Skip("skipping in short mode")
294 }
295
296 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test"),
297 "cmplxdivide.go",
298 "directive.go",
299 "directive2.go",
300 "embedfunc.go",
301 "embedvers.go",
302 "linkname2.go",
303 "linkname3.go",
304 )
305 }
306
307 func TestStdFixed(t *testing.T) {
308 testenv.MustHaveGoBuild(t)
309
310 if testing.Short() && testenv.Builder() == "" {
311 t.Skip("skipping in short mode")
312 }
313
314 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "fixedbugs"),
315 "bug248.go", "bug302.go", "bug369.go",
316 "bug398.go",
317 "issue6889.go",
318 "issue11362.go",
319 "issue16369.go",
320 "issue18459.go",
321 "issue18882.go",
322 "issue20027.go",
323 "issue20529.go",
324 "issue22200.go",
325 "issue22200b.go",
326 "issue25507.go",
327 "issue20780.go",
328 "bug251.go",
329 "issue42058a.go",
330 "issue42058b.go",
331 "issue48097.go",
332 "issue48230.go",
333 "issue49767.go",
334 "issue49814.go",
335 "issue56103.go",
336 "issue52697.go",
337
338
339
340 "bug514.go",
341 "issue40954.go",
342 "issue42032.go",
343 "issue42076.go",
344 "issue46903.go",
345 "issue51733.go",
346 "notinheap2.go",
347 "notinheap3.go",
348 )
349 }
350
351 func TestStdKen(t *testing.T) {
352 testenv.MustHaveGoBuild(t)
353
354 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
355 }
356
357
358 var excluded = map[string]bool{
359 "builtin": true,
360 "cmd/compile/internal/ssa/_gen": true,
361 }
362
363
364
365
366
367
368 var printPackageMu sync.Mutex
369
370
371 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
372 fset := token.NewFileSet()
373
374
375 var files []*ast.File
376 for _, filename := range filenames {
377 file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
378 if err != nil {
379 return nil, err
380 }
381
382 files = append(files, file)
383 }
384
385 if testing.Verbose() {
386 printPackageMu.Lock()
387 fmt.Println("package", files[0].Name.Name)
388 for _, filename := range filenames {
389 fmt.Println("\t", filename)
390 }
391 printPackageMu.Unlock()
392 }
393
394
395 var errs []error
396 conf := Config{
397 Error: func(err error) {
398 errs = append(errs, err)
399 },
400 Importer: importer,
401 }
402 info := Info{Uses: make(map[*ast.Ident]Object)}
403 pkg, _ := conf.Check(path, fset, files, &info)
404 err := errors.Join(errs...)
405 if err != nil {
406 return pkg, err
407 }
408
409
410
411
412 errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0)
413 for id, obj := range info.Uses {
414 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
415 if predeclared == (obj.Pkg() != nil) {
416 posn := fset.Position(id.Pos())
417 if predeclared {
418 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
419 } else {
420 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
421 }
422 }
423 }
424
425 return pkg, nil
426 }
427
428
429 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
430 ctxt := build.Default
431 ctxt.CgoEnabled = false
432 pkg, err := ctxt.ImportDir(dir, 0)
433 if err != nil {
434 if _, nogo := err.(*build.NoGoError); nogo {
435 return nil, nil
436 }
437 return nil, err
438 }
439 if excluded[pkg.ImportPath] {
440 return nil, nil
441 }
442 if slices.Contains(strings.Split(pkg.ImportPath, "/"), "_asm") {
443
444
445 return nil, nil
446 }
447 var filenames []string
448 for _, name := range pkg.GoFiles {
449 filenames = append(filenames, filepath.Join(pkg.Dir, name))
450 }
451 if includeTest {
452 for _, name := range pkg.TestGoFiles {
453 filenames = append(filenames, filepath.Join(pkg.Dir, name))
454 }
455 }
456 return filenames, nil
457 }
458
459 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...any)) {
460 w := walker{pkgh, errh}
461 w.walk(dir)
462 }
463
464 type walker struct {
465 pkgh func(dir string, filenames []string)
466 errh func(args ...any)
467 }
468
469 func (w *walker) walk(dir string) {
470 files, err := os.ReadDir(dir)
471 if err != nil {
472 w.errh(err)
473 return
474 }
475
476
477
478
479 pkgFiles, err := pkgFilenames(dir, false)
480 if err != nil {
481 w.errh(err)
482 return
483 }
484 if pkgFiles != nil {
485 w.pkgh(dir, pkgFiles)
486 }
487
488
489 for _, f := range files {
490 if f.IsDir() && f.Name() != "testdata" {
491 w.walk(filepath.Join(dir, f.Name()))
492 }
493 }
494 }
495
View as plain text