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 "issue68054.go",
338 "issue68580.go",
339 "issue73309.go",
340 "issue73309b.go",
341
342
343
344 "bug514.go",
345 "issue40954.go",
346 "issue42032.go",
347 "issue42076.go",
348 "issue46903.go",
349 "issue51733.go",
350 "notinheap2.go",
351 "notinheap3.go",
352 )
353 }
354
355 func TestStdKen(t *testing.T) {
356 testenv.MustHaveGoBuild(t)
357
358 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
359 }
360
361
362 var excluded = map[string]bool{
363 "builtin": true,
364 "cmd/compile/internal/ssa/_gen": true,
365 }
366
367
368
369
370
371
372 var printPackageMu sync.Mutex
373
374
375 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
376 fset := token.NewFileSet()
377
378
379 var files []*ast.File
380 for _, filename := range filenames {
381 file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
382 if err != nil {
383 return nil, err
384 }
385
386 files = append(files, file)
387 }
388
389 if testing.Verbose() {
390 printPackageMu.Lock()
391 fmt.Println("package", files[0].Name.Name)
392 for _, filename := range filenames {
393 fmt.Println("\t", filename)
394 }
395 printPackageMu.Unlock()
396 }
397
398
399 var errs []error
400 conf := Config{
401 Error: func(err error) {
402 errs = append(errs, err)
403 },
404 Importer: importer,
405 }
406 info := Info{Uses: make(map[*ast.Ident]Object)}
407 pkg, _ := conf.Check(path, fset, files, &info)
408 err := errors.Join(errs...)
409 if err != nil {
410 return pkg, err
411 }
412
413
414
415
416 errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0)
417 for id, obj := range info.Uses {
418 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
419 if predeclared == (obj.Pkg() != nil) {
420 posn := fset.Position(id.Pos())
421 if predeclared {
422 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
423 } else {
424 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
425 }
426 }
427 }
428
429 return pkg, nil
430 }
431
432
433 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
434 ctxt := build.Default
435 ctxt.CgoEnabled = false
436 pkg, err := ctxt.ImportDir(dir, 0)
437 if err != nil {
438 if _, nogo := err.(*build.NoGoError); nogo {
439 return nil, nil
440 }
441 return nil, err
442 }
443 if excluded[pkg.ImportPath] {
444 return nil, nil
445 }
446 if slices.Contains(strings.Split(pkg.ImportPath, "/"), "_asm") {
447
448
449 return nil, nil
450 }
451 var filenames []string
452 for _, name := range pkg.GoFiles {
453 filenames = append(filenames, filepath.Join(pkg.Dir, name))
454 }
455 if includeTest {
456 for _, name := range pkg.TestGoFiles {
457 filenames = append(filenames, filepath.Join(pkg.Dir, name))
458 }
459 }
460 return filenames, nil
461 }
462
463 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...any)) {
464 w := walker{pkgh, errh}
465 w.walk(dir)
466 }
467
468 type walker struct {
469 pkgh func(dir string, filenames []string)
470 errh func(args ...any)
471 }
472
473 func (w *walker) walk(dir string) {
474 files, err := os.ReadDir(dir)
475 if err != nil {
476 w.errh(err)
477 return
478 }
479
480
481
482
483 pkgFiles, err := pkgFilenames(dir, false)
484 if err != nil {
485 w.errh(err)
486 return
487 }
488 if pkgFiles != nil {
489 w.pkgh(dir, pkgFiles)
490 }
491
492
493 for _, f := range files {
494 if f.IsDir() && f.Name() != "testdata" {
495 w.walk(filepath.Join(dir, f.Name()))
496 }
497 }
498 }
499
View as plain text