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