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 types.Check by using it to
6 // typecheck the standard library and tests.
31 // The cmd/*/internal packages may have been deleted as part of a binary
32 // release. Import from source instead.
34 // (See https://golang.org/issue/43232 and
35 // https://github.com/golang/build/blob/df58bbac082bc87c4a3cdfe336d1ffe60bbaa916/cmd/release/release.go#L533-L545.)
37 // Use the same importer for all std lib tests to
38 // avoid repeated importing of the same packages.
39 var stdLibImporter = importer.ForCompiler(token.NewFileSet(), "source", nil)
41 func TestStdlib(t *testing.T) {
43 t.Skip("skipping in short mode")
46 testenv.MustHaveGoBuild(t)
48 // Collect non-test files.
49 dirFiles := make(map[string][]string)
50 root := filepath.Join(testenv.GOROOT(t), "src")
51 walkPkgDirs(root, func(dir string, filenames []string) {
52 dirFiles[dir] = filenames
57 pkgs: make(map[string]*futurePackage),
62 // Though we read files while parsing, type-checking is otherwise CPU bound.
64 // This doesn't achieve great CPU utilization as many packages may block
65 // waiting for a common import, but in combination with the non-deterministic
66 // map iteration below this should provide decent coverage of concurrent
67 // type-checking (see golang/go#47729).
68 cpulimit := make(chan struct{}, runtime.GOMAXPROCS(0))
71 for dir := range dirFiles {
74 cpulimit <- struct{}{}
82 _, err := c.getDirPackage(dir)
84 t.Errorf("error checking %s: %v", dir, err)
91 if testing.Verbose() {
92 fmt.Println(len(dirFiles), "packages typechecked in", time.Since(start))
96 // stdlibChecker implements concurrent type-checking of the packages defined by
97 // dirFiles, which must define a closed set of packages (such as GOROOT/src).
98 type stdlibChecker struct {
99 dirFiles map[string][]string // non-test files per directory; must be pre-populated
102 pkgs map[string]*futurePackage // future cache of type-checking results
105 // A futurePackage is a future result of type-checking.
106 type futurePackage struct {
107 done chan struct{} // guards pkg and err
112 func (c *stdlibChecker) Import(path string) (*Package, error) {
113 panic("unimplemented: use ImportFrom")
116 func (c *stdlibChecker) ImportFrom(path, dir string, _ ImportMode) (*Package, error) {
117 if path == "unsafe" {
118 // unsafe cannot be type checked normally.
122 p, err := build.Default.Import(path, dir, build.FindOnly)
127 pkg, err := c.getDirPackage(p.Dir)
129 // As long as pkg is non-nil, avoid redundant errors related to failed
130 // imports. TestStdlib will collect errors once for each package.
136 // getDirPackage gets the package defined in dir from the future cache.
138 // If this is the first goroutine requesting the package, getDirPackage
140 func (c *stdlibChecker) getDirPackage(dir string) (*Package, error) {
142 fut, ok := c.pkgs[dir]
144 // First request for this package dir; type check.
145 fut = &futurePackage{
146 done: make(chan struct{}),
149 files, ok := c.dirFiles[dir]
152 fut.err = fmt.Errorf("no files for %s", dir)
154 // Using dir as the package path here may be inconsistent with the behavior
155 // of a normal importer, but is sufficient as dir is by construction unique
157 fut.pkg, fut.err = typecheckFiles(dir, files, c)
161 // Otherwise, await the result.
165 return fut.pkg, fut.err
168 // firstComment returns the contents of the first non-empty comment in
169 // the given file, "skip", or the empty string. No matter the present
170 // comments, if any of them contains a build tag, the result is always
171 // "skip". Only comments before the "package" token and within the first
172 // 4K of the file are considered.
173 func firstComment(filename string) string {
174 f, err := os.Open(filename)
180 var src [4 << 10]byte // read at most 4KB
181 n, _ := f.Read(src[:])
184 var s scanner.Scanner
185 s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil /* ignore errors */, scanner.ScanComments)
187 _, tok, lit := s.Scan()
190 // remove trailing */ of multi-line comment
192 lit = lit[:len(lit)-2]
194 contents := strings.TrimSpace(lit[2:])
195 if strings.HasPrefix(contents, "+build ") {
199 first = contents // contents may be "" but that's ok
201 // continue as we may still see build tags
203 case token.PACKAGE, token.EOF:
209 func testTestDir(t *testing.T, path string, ignore ...string) {
210 files, err := os.ReadDir(path)
212 // cmd/distpack deletes GOROOT/test, so skip the test if it isn't present.
213 // cmd/distpack also requires GOROOT/VERSION to exist, so use that to
214 // suppress false-positive skips.
215 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "test")); os.IsNotExist(err) {
216 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
217 t.Skipf("skipping: GOROOT/test not present")
223 excluded := make(map[string]bool)
224 for _, filename := range ignore {
225 excluded[filename] = true
228 fset := token.NewFileSet()
229 for _, f := range files {
230 // filter directory contents
231 if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
235 // get per-file instructions
236 expectErrors := false
237 filename := filepath.Join(path, f.Name())
239 if comment := firstComment(filename); comment != "" {
240 fields := strings.Fields(comment)
242 case "skip", "compiledir":
243 continue // ignore this file
246 for _, arg := range fields[1:] {
247 if arg == "-0" || arg == "-+" || arg == "-std" {
248 // Marked explicitly as not expecting errors (-0),
249 // or marked as compiling runtime/stdlib, which is only done
250 // to trigger runtime/stdlib-only error output.
251 // In both cases, the code should typecheck.
255 const prefix = "-lang="
256 if strings.HasPrefix(arg, prefix) {
257 goVersion = arg[len(prefix):]
263 // parse and type-check file
264 file, err := parser.ParseFile(fset, filename, nil, 0)
267 GoVersion: goVersion,
268 Importer: stdLibImporter,
270 _, err = conf.Check(filename, fset, []*ast.File{file}, nil)
275 t.Errorf("expected errors but found none in %s", filename)
285 func TestStdTest(t *testing.T) {
286 testenv.MustHaveGoBuild(t)
288 if testing.Short() && testenv.Builder() == "" {
289 t.Skip("skipping in short mode")
292 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test"),
293 "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
294 "directive.go", // tests compiler rejection of bad directive placement - ignore
295 "directive2.go", // tests compiler rejection of bad directive placement - ignore
296 "embedfunc.go", // tests //go:embed
297 "embedvers.go", // tests //go:embed
298 "linkname2.go", // go/types doesn't check validity of //go:xxx directives
299 "linkname3.go", // go/types doesn't check validity of //go:xxx directives
303 func TestStdFixed(t *testing.T) {
304 testenv.MustHaveGoBuild(t)
306 if testing.Short() && testenv.Builder() == "" {
307 t.Skip("skipping in short mode")
310 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "fixedbugs"),
311 "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore
312 "issue6889.go", // gc-specific test
313 "issue11362.go", // canonical import path check
314 "issue16369.go", // go/types handles this correctly - not an issue
315 "issue18459.go", // go/types doesn't check validity of //go:xxx directives
316 "issue18882.go", // go/types doesn't check validity of //go:xxx directives
317 "issue20529.go", // go/types does not have constraints on stack size
318 "issue22200.go", // go/types does not have constraints on stack size
319 "issue22200b.go", // go/types does not have constraints on stack size
320 "issue25507.go", // go/types does not have constraints on stack size
321 "issue20780.go", // go/types does not have constraints on stack size
322 "bug251.go", // go.dev/issue/34333 which was exposed with fix for go.dev/issue/34151
323 "issue42058a.go", // go/types does not have constraints on channel element size
324 "issue42058b.go", // go/types does not have constraints on channel element size
325 "issue48097.go", // go/types doesn't check validity of //go:xxx directives, and non-init bodyless function
326 "issue48230.go", // go/types doesn't check validity of //go:xxx directives
327 "issue49767.go", // go/types does not have constraints on channel element size
328 "issue49814.go", // go/types does not have constraints on array size
329 "issue56103.go", // anonymous interface cycles; will be a type checker error in 1.22
331 // These tests requires runtime/cgo.Incomplete, which is only available on some platforms.
332 // However, go/types does not know about build constraints.
344 func TestStdKen(t *testing.T) {
345 testenv.MustHaveGoBuild(t)
347 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
350 // Package paths of excluded packages.
351 var excluded = map[string]bool{
354 // See go.dev/issue/46027: some imports are missing for this submodule.
355 "crypto/internal/edwards25519/field/_asm": true,
356 "crypto/internal/bigmod/_asm": true,
359 // printPackageMu synchronizes the printing of type-checked package files in
360 // the typecheckFiles function.
362 // Without synchronization, package files may be interleaved during concurrent
364 var printPackageMu sync.Mutex
366 // typecheckFiles typechecks the given package files.
367 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
368 fset := token.NewFileSet()
370 // Parse package files.
371 var files []*ast.File
372 for _, filename := range filenames {
373 file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
378 files = append(files, file)
381 if testing.Verbose() {
382 printPackageMu.Lock()
383 fmt.Println("package", files[0].Name.Name)
384 for _, filename := range filenames {
385 fmt.Println("\t", filename)
387 printPackageMu.Unlock()
390 // Typecheck package files.
393 Error: func(err error) {
394 errs = append(errs, err)
398 info := Info{Uses: make(map[*ast.Ident]Object)}
399 pkg, _ := conf.Check(path, fset, files, &info)
400 err := errors.Join(errs...)
405 // Perform checks of API invariants.
407 // All Objects have a package, except predeclared ones.
408 errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
409 for id, obj := range info.Uses {
410 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
411 if predeclared == (obj.Pkg() != nil) {
412 posn := fset.Position(id.Pos())
414 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
416 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
424 // pkgFilenames returns the list of package filenames for the given directory.
425 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
426 ctxt := build.Default
427 ctxt.CgoEnabled = false
428 pkg, err := ctxt.ImportDir(dir, 0)
430 if _, nogo := err.(*build.NoGoError); nogo {
431 return nil, nil // no *.go files, not an error
435 if excluded[pkg.ImportPath] {
438 var filenames []string
439 for _, name := range pkg.GoFiles {
440 filenames = append(filenames, filepath.Join(pkg.Dir, name))
443 for _, name := range pkg.TestGoFiles {
444 filenames = append(filenames, filepath.Join(pkg.Dir, name))
447 return filenames, nil
450 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...any)) {
451 w := walker{pkgh, errh}
456 pkgh func(dir string, filenames []string)
457 errh func(args ...any)
460 func (w *walker) walk(dir string) {
461 files, err := os.ReadDir(dir)
467 // apply pkgh to the files in directory dir
469 // Don't get test files as these packages are imported.
470 pkgFiles, err := pkgFilenames(dir, false)
476 w.pkgh(dir, pkgFiles)
479 // traverse subdirectories, but don't walk into testdata
480 for _, f := range files {
481 if f.IsDir() && f.Name() != "testdata" {
482 w.walk(filepath.Join(dir, f.Name()))