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