1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // This file tests types2.Check by using it to
6 // typecheck the standard library and tests.
12 "cmd/compile/internal/syntax"
25 . "cmd/compile/internal/types2"
28 var stdLibImporter = defaultImporter()
30 func TestStdlib(t *testing.T) {
32 t.Skip("skipping in short mode")
35 testenv.MustHaveGoBuild(t)
37 // Collect non-test files.
38 dirFiles := make(map[string][]string)
39 root := filepath.Join(testenv.GOROOT(t), "src")
40 walkPkgDirs(root, func(dir string, filenames []string) {
41 dirFiles[dir] = filenames
46 pkgs: make(map[string]*futurePackage),
51 // Though we read files while parsing, type-checking is otherwise CPU bound.
53 // This doesn't achieve great CPU utilization as many packages may block
54 // waiting for a common import, but in combination with the non-deterministic
55 // map iteration below this should provide decent coverage of concurrent
56 // type-checking (see golang/go#47729).
57 cpulimit := make(chan struct{}, runtime.GOMAXPROCS(0))
60 for dir := range dirFiles {
63 cpulimit <- struct{}{}
71 _, err := c.getDirPackage(dir)
73 t.Errorf("error checking %s: %v", dir, err)
80 if testing.Verbose() {
81 fmt.Println(len(dirFiles), "packages typechecked in", time.Since(start))
85 // stdlibChecker implements concurrent type-checking of the packages defined by
86 // dirFiles, which must define a closed set of packages (such as GOROOT/src).
87 type stdlibChecker struct {
88 dirFiles map[string][]string // non-test files per directory; must be pre-populated
91 pkgs map[string]*futurePackage // future cache of type-checking results
94 // A futurePackage is a future result of type-checking.
95 type futurePackage struct {
96 done chan struct{} // guards pkg and err
101 func (c *stdlibChecker) Import(path string) (*Package, error) {
102 panic("unimplemented: use ImportFrom")
105 func (c *stdlibChecker) ImportFrom(path, dir string, _ ImportMode) (*Package, error) {
106 if path == "unsafe" {
107 // unsafe cannot be type checked normally.
111 p, err := build.Default.Import(path, dir, build.FindOnly)
116 pkg, err := c.getDirPackage(p.Dir)
118 // As long as pkg is non-nil, avoid redundant errors related to failed
119 // imports. TestStdlib will collect errors once for each package.
125 // getDirPackage gets the package defined in dir from the future cache.
127 // If this is the first goroutine requesting the package, getDirPackage
129 func (c *stdlibChecker) getDirPackage(dir string) (*Package, error) {
131 fut, ok := c.pkgs[dir]
133 // First request for this package dir; type check.
134 fut = &futurePackage{
135 done: make(chan struct{}),
138 files, ok := c.dirFiles[dir]
141 fut.err = fmt.Errorf("no files for %s", dir)
143 // Using dir as the package path here may be inconsistent with the behavior
144 // of a normal importer, but is sufficient as dir is by construction unique
146 fut.pkg, fut.err = typecheckFiles(dir, files, c)
150 // Otherwise, await the result.
154 return fut.pkg, fut.err
157 // firstComment returns the contents of the first non-empty comment in
158 // the given file, "skip", or the empty string. No matter the present
159 // comments, if any of them contains a build tag, the result is always
160 // "skip". Only comments within the first 4K of the file are considered.
161 // TODO(gri) should only read until we see "package" token.
162 func firstComment(filename string) (first string) {
163 f, err := os.Open(filename)
170 var buf [4 << 10]byte
171 n, _ := f.Read(buf[:])
172 src := bytes.NewBuffer(buf[:n])
174 // TODO(gri) we need a better way to terminate CommentsDo
176 if p := recover(); p != nil {
177 if s, ok := p.(string); ok {
183 syntax.CommentsDo(src, func(_, _ uint, text string) {
185 return // not a comment
188 // extract comment text
190 text = text[:len(text)-2]
192 text = strings.TrimSpace(text[2:])
194 if strings.HasPrefix(text, "go:build ") {
198 first = text // text may be "" but that's ok
200 // continue as we may still see build tags
206 func testTestDir(t *testing.T, path string, ignore ...string) {
207 files, err := os.ReadDir(path)
209 // cmd/distpack deletes GOROOT/test, so skip the test if it isn't present.
210 // cmd/distpack also requires GOROOT/VERSION to exist, so use that to
211 // suppress false-positive skips.
212 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "test")); os.IsNotExist(err) {
213 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
214 t.Skipf("skipping: GOROOT/test not present")
220 excluded := make(map[string]bool)
221 for _, filename := range ignore {
222 excluded[filename] = true
225 for _, f := range files {
226 // filter directory contents
227 if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
231 // get per-file instructions
232 expectErrors := false
233 filename := filepath.Join(path, f.Name())
235 if comment := firstComment(filename); comment != "" {
236 if strings.Contains(comment, "-goexperiment") {
237 continue // ignore this file
239 fields := strings.Fields(comment)
241 case "skip", "compiledir":
242 continue // ignore this file
245 for _, arg := range fields[1:] {
246 if arg == "-0" || arg == "-+" || arg == "-std" {
247 // Marked explicitly as not expecting errors (-0),
248 // or marked as compiling runtime/stdlib, which is only done
249 // to trigger runtime/stdlib-only error output.
250 // In both cases, the code should typecheck.
254 const prefix = "-lang="
255 if strings.HasPrefix(arg, prefix) {
256 goVersion = arg[len(prefix):]
262 // parse and type-check file
263 if testing.Verbose() {
264 fmt.Println("\t", filename)
266 file, err := syntax.ParseFile(filename, nil, nil, 0)
269 GoVersion: goVersion,
270 Importer: stdLibImporter,
272 _, err = conf.Check(filename, []*syntax.File{file}, nil)
277 t.Errorf("expected errors but found none in %s", filename)
287 func TestStdTest(t *testing.T) {
288 testenv.MustHaveGoBuild(t)
290 if testing.Short() && testenv.Builder() == "" {
291 t.Skip("skipping in short mode")
294 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test"),
295 "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
296 "directive.go", // tests compiler rejection of bad directive placement - ignore
297 "directive2.go", // tests compiler rejection of bad directive placement - ignore
298 "embedfunc.go", // tests //go:embed
299 "embedvers.go", // tests //go:embed
300 "linkname2.go", // types2 doesn't check validity of //go:xxx directives
301 "linkname3.go", // types2 doesn't check validity of //go:xxx directives
305 func TestStdFixed(t *testing.T) {
306 testenv.MustHaveGoBuild(t)
308 if testing.Short() && testenv.Builder() == "" {
309 t.Skip("skipping in short mode")
312 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "fixedbugs"),
313 "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore
314 "issue6889.go", // gc-specific test
315 "issue11362.go", // canonical import path check
316 "issue16369.go", // types2 handles this correctly - not an issue
317 "issue18459.go", // types2 doesn't check validity of //go:xxx directives
318 "issue18882.go", // types2 doesn't check validity of //go:xxx directives
319 "issue20529.go", // types2 does not have constraints on stack size
320 "issue22200.go", // types2 does not have constraints on stack size
321 "issue22200b.go", // types2 does not have constraints on stack size
322 "issue25507.go", // types2 does not have constraints on stack size
323 "issue20780.go", // types2 does not have constraints on stack size
324 "issue42058a.go", // types2 does not have constraints on channel element size
325 "issue42058b.go", // types2 does not have constraints on channel element size
326 "issue48097.go", // go/types doesn't check validity of //go:xxx directives, and non-init bodyless function
327 "issue48230.go", // go/types doesn't check validity of //go:xxx directives
328 "issue49767.go", // go/types does not have constraints on channel element size
329 "issue49814.go", // go/types does not have constraints on array size
330 "issue56103.go", // anonymous interface cycles; will be a type checker error in 1.22
331 "issue52697.go", // types2 does not have constraints on stack size
333 // These tests requires runtime/cgo.Incomplete, which is only available on some platforms.
334 // However, types2 does not know about build constraints.
346 func TestStdKen(t *testing.T) {
347 testenv.MustHaveGoBuild(t)
349 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
352 // Package paths of excluded packages.
353 var excluded = map[string]bool{
356 // go.dev/issue/46027: some imports are missing for this submodule.
357 "crypto/internal/edwards25519/field/_asm": true,
358 "crypto/internal/bigmod/_asm": true,
361 // printPackageMu synchronizes the printing of type-checked package files in
362 // the typecheckFiles function.
364 // Without synchronization, package files may be interleaved during concurrent
366 var printPackageMu sync.Mutex
368 // typecheckFiles typechecks the given package files.
369 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
370 // Parse package files.
371 var files []*syntax.File
372 for _, filename := range filenames {
374 errh := func(err error) { errs = append(errs, err) }
375 file, err := syntax.ParseFile(filename, errh, nil, 0)
377 return nil, errors.Join(errs...)
380 files = append(files, file)
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)
389 printPackageMu.Unlock()
392 // Typecheck package files.
395 Error: func(err error) {
396 errs = append(errs, err)
400 info := Info{Uses: make(map[*syntax.Name]Object)}
401 pkg, _ := conf.Check(path, files, &info)
402 err := errors.Join(errs...)
407 // Perform checks of API invariants.
409 // All Objects have a package, except predeclared ones.
410 errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
411 for id, obj := range info.Uses {
412 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
413 if predeclared == (obj.Pkg() != nil) {
416 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
418 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
426 // pkgFilenames returns the list of package filenames for the given directory.
427 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
428 ctxt := build.Default
429 ctxt.CgoEnabled = false
430 pkg, err := ctxt.ImportDir(dir, 0)
432 if _, nogo := err.(*build.NoGoError); nogo {
433 return nil, nil // no *.go files, not an error
437 if excluded[pkg.ImportPath] {
440 var filenames []string
441 for _, name := range pkg.GoFiles {
442 filenames = append(filenames, filepath.Join(pkg.Dir, name))
445 for _, name := range pkg.TestGoFiles {
446 filenames = append(filenames, filepath.Join(pkg.Dir, name))
449 return filenames, nil
452 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...interface{})) {
453 w := walker{pkgh, errh}
458 pkgh func(dir string, filenames []string)
459 errh func(args ...any)
462 func (w *walker) walk(dir string) {
463 files, err := os.ReadDir(dir)
469 // apply pkgh to the files in directory dir
471 // Don't get test files as these packages are imported.
472 pkgFiles, err := pkgFilenames(dir, false)
478 w.pkgh(dir, pkgFiles)
481 // traverse subdirectories, but don't walk into testdata
482 for _, f := range files {
483 if f.IsDir() && f.Name() != "testdata" {
484 w.walk(filepath.Join(dir, f.Name()))