go/types: move gotype command into this directory

This is a copy of x/tools/cmd/gotype/gotype.go with the corresponding
x/tools/cmd/gotype/doc.go prepended and including a build tag (ignore).

This way, go/types can be built unaffected. If we need the gotype command,
it is trivially built in the go/types directory with: go build gotype.go .

Fixes #12303.

Change-Id: I2d792fcb39719cc5cc300f657e4735901cd20faa
Reviewed-on: https://go-review.googlesource.com/15152
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/src/go/types/gotype.go b/src/go/types/gotype.go
new file mode 100644
index 0000000..0a36c08
--- /dev/null
+++ b/src/go/types/gotype.go
@@ -0,0 +1,322 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+// Build this command explicitly: go build gotype.go
+
+/*
+The gotype command does syntactic and semantic analysis of Go files
+and packages like the front-end of a Go compiler. Errors are reported
+if the analysis fails; otherwise gotype is quiet (unless -v is set).
+
+Without a list of paths, gotype reads from standard input, which
+must provide a single Go source file defining a complete package.
+
+If a single path is specified that is a directory, gotype checks
+the Go files in that directory; they must all belong to the same
+package.
+
+Otherwise, each path must be the filename of Go file belonging to
+the same package.
+
+Usage:
+	gotype [flags] [path...]
+
+The flags are:
+	-a
+		use all (incl. _test.go) files when processing a directory
+	-e
+		report all errors (not just the first 10)
+	-v
+		verbose mode
+	-c
+		compiler used to compile packages (gc or gccgo); default: gc
+		(gotype based on Go1.5 and up only)
+	-gccgo
+		use gccimporter instead of gcimporter
+		(gotype based on Go1.4 and before only)
+
+Debugging flags:
+	-seq
+		parse sequentially, rather than in parallel
+	-ast
+		print AST (forces -seq)
+	-trace
+		print parse trace (forces -seq)
+	-comments
+		parse comments (ignored unless -ast or -trace is provided)
+
+Examples:
+
+To check the files a.go, b.go, and c.go:
+
+	gotype a.go b.go c.go
+
+To check an entire package in the directory dir and print the processed files:
+
+	gotype -v dir
+
+To check an entire package including tests in the local directory:
+
+	gotype -a .
+
+To verify the output of a pipe:
+
+	echo "package foo" | gotype
+
+*/
+package main
+
+import (
+	"flag"
+	"fmt"
+	"go/ast"
+	"go/build"
+	"go/importer"
+	"go/parser"
+	"go/scanner"
+	"go/token"
+	"go/types"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+var (
+	// main operation modes
+	allFiles  = flag.Bool("a", false, "use all (incl. _test.go) files when processing a directory")
+	allErrors = flag.Bool("e", false, "report all errors (not just the first 10)")
+	verbose   = flag.Bool("v", false, "verbose mode")
+	gccgo     = flag.Bool("gccgo", false, "use gccgoimporter instead of gcimporter")
+
+	// debugging support
+	sequential    = flag.Bool("seq", false, "parse sequentially, rather than in parallel")
+	printAST      = flag.Bool("ast", false, "print AST (forces -seq)")
+	printTrace    = flag.Bool("trace", false, "print parse trace (forces -seq)")
+	parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)")
+)
+
+var (
+	fset       = token.NewFileSet()
+	errorCount = 0
+	parserMode parser.Mode
+	sizes      types.Sizes
+)
+
+func initParserMode() {
+	if *allErrors {
+		parserMode |= parser.AllErrors
+	}
+	if *printTrace {
+		parserMode |= parser.Trace
+	}
+	if *parseComments && (*printAST || *printTrace) {
+		parserMode |= parser.ParseComments
+	}
+}
+
+func initSizes() {
+	wordSize := 8
+	maxAlign := 8
+	switch build.Default.GOARCH {
+	case "386", "arm":
+		wordSize = 4
+		maxAlign = 4
+		// add more cases as needed
+	}
+	sizes = &types.StdSizes{WordSize: int64(wordSize), MaxAlign: int64(maxAlign)}
+}
+
+func usage() {
+	fmt.Fprintln(os.Stderr, "usage: gotype [flags] [path ...]")
+	flag.PrintDefaults()
+	os.Exit(2)
+}
+
+func report(err error) {
+	scanner.PrintError(os.Stderr, err)
+	if list, ok := err.(scanner.ErrorList); ok {
+		errorCount += len(list)
+		return
+	}
+	errorCount++
+}
+
+// parse may be called concurrently
+func parse(filename string, src interface{}) (*ast.File, error) {
+	if *verbose {
+		fmt.Println(filename)
+	}
+	file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently
+	if *printAST {
+		ast.Print(fset, file)
+	}
+	return file, err
+}
+
+func parseStdin() (*ast.File, error) {
+	src, err := ioutil.ReadAll(os.Stdin)
+	if err != nil {
+		return nil, err
+	}
+	return parse("<standard input>", src)
+}
+
+func parseFiles(filenames []string) ([]*ast.File, error) {
+	files := make([]*ast.File, len(filenames))
+
+	if *sequential {
+		for i, filename := range filenames {
+			var err error
+			files[i], err = parse(filename, nil)
+			if err != nil {
+				return nil, err // leave unfinished goroutines hanging
+			}
+		}
+	} else {
+		type parseResult struct {
+			file *ast.File
+			err  error
+		}
+
+		out := make(chan parseResult)
+		for _, filename := range filenames {
+			go func(filename string) {
+				file, err := parse(filename, nil)
+				out <- parseResult{file, err}
+			}(filename)
+		}
+
+		for i := range filenames {
+			res := <-out
+			if res.err != nil {
+				return nil, res.err // leave unfinished goroutines hanging
+			}
+			files[i] = res.file
+		}
+	}
+
+	return files, nil
+}
+
+func parseDir(dirname string) ([]*ast.File, error) {
+	ctxt := build.Default
+	pkginfo, err := ctxt.ImportDir(dirname, 0)
+	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
+		return nil, err
+	}
+	filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
+	if *allFiles {
+		filenames = append(filenames, pkginfo.TestGoFiles...)
+	}
+
+	// complete file names
+	for i, filename := range filenames {
+		filenames[i] = filepath.Join(dirname, filename)
+	}
+
+	return parseFiles(filenames)
+}
+
+func getPkgFiles(args []string) ([]*ast.File, error) {
+	if len(args) == 0 {
+		// stdin
+		file, err := parseStdin()
+		if err != nil {
+			return nil, err
+		}
+		return []*ast.File{file}, nil
+	}
+
+	if len(args) == 1 {
+		// possibly a directory
+		path := args[0]
+		info, err := os.Stat(path)
+		if err != nil {
+			return nil, err
+		}
+		if info.IsDir() {
+			return parseDir(path)
+		}
+	}
+
+	// list of files
+	return parseFiles(args)
+}
+
+func checkPkgFiles(files []*ast.File) {
+	compiler := "gc"
+	if *gccgo {
+		compiler = "gccgo"
+	}
+	type bailout struct{}
+	conf := types.Config{
+		FakeImportC: true,
+		Error: func(err error) {
+			if !*allErrors && errorCount >= 10 {
+				panic(bailout{})
+			}
+			report(err)
+		},
+		Importer: importer.For(compiler, nil),
+		Sizes:    sizes,
+	}
+
+	defer func() {
+		switch p := recover().(type) {
+		case nil, bailout:
+			// normal return or early exit
+		default:
+			// re-panic
+			panic(p)
+		}
+	}()
+
+	const path = "pkg" // any non-empty string will do for now
+	conf.Check(path, fset, files, nil)
+}
+
+func printStats(d time.Duration) {
+	fileCount := 0
+	lineCount := 0
+	fset.Iterate(func(f *token.File) bool {
+		fileCount++
+		lineCount += f.LineCount()
+		return true
+	})
+
+	fmt.Printf(
+		"%s (%d files, %d lines, %d lines/s)\n",
+		d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()),
+	)
+}
+
+func main() {
+	flag.Usage = usage
+	flag.Parse()
+	if *printAST || *printTrace {
+		*sequential = true
+	}
+	initParserMode()
+	initSizes()
+
+	start := time.Now()
+
+	files, err := getPkgFiles(flag.Args())
+	if err != nil {
+		report(err)
+		os.Exit(2)
+	}
+
+	checkPkgFiles(files)
+	if errorCount > 0 {
+		os.Exit(2)
+	}
+
+	if *verbose {
+		printStats(time.Since(start))
+	}
+}