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
336
337
338 "bug514.go",
339 "issue40954.go",
340 "issue42032.go",
341 "issue42076.go",
342 "issue46903.go",
343 "issue51733.go",
344 "notinheap2.go",
345 "notinheap3.go",
346 )
347 }
348
349 func TestStdKen(t *testing.T) {
350 testenv.MustHaveGoBuild(t)
351
352 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
353 }
354
355
356 var excluded = map[string]bool{
357 "builtin": true,
358 "cmd/compile/internal/ssa/_gen": true,
359 }
360
361
362
363
364
365
366 var printPackageMu sync.Mutex
367
368
369 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
370
371 var files []*syntax.File
372 for _, filename := range filenames {
373 var errs []error
374 errh := func(err error) { errs = append(errs, err) }
375 file, err := syntax.ParseFile(filename, errh, nil, 0)
376 if err != nil {
377 return nil, errors.Join(errs...)
378 }
379
380 files = append(files, file)
381 }
382
383 if testing.Verbose() {
384 printPackageMu.Lock()
385 fmt.Println("package", files[0].PkgName.Value)
386 for _, filename := range filenames {
387 fmt.Println("\t", filename)
388 }
389 printPackageMu.Unlock()
390 }
391
392
393 var errs []error
394 conf := Config{
395 Error: func(err error) {
396 errs = append(errs, err)
397 },
398 Importer: importer,
399 EnableAlias: true,
400 }
401 info := Info{Uses: make(map[*syntax.Name]Object)}
402 pkg, _ := conf.Check(path, 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 := 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 ...interface{})) {
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