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