bind: vendor x/tools/go/loader

This breaks our dependency on the x/tools repository, which has a
tendency to change in unexpected ways. It also means we can use the
version of go/types that ships with Go 1.5.

Along the way, it appears that cgo processing has changed slightly.
The old check for cgo files apparently wasn't working, so I removed
it.

Change-Id: I14378e9df9cd65c5ab61b47728ba0d56f31cdf76
Reviewed-on: https://go-review.googlesource.com/12680
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/bind/bind.go b/bind/bind.go
index 58618e1..46286a2 100644
--- a/bind/bind.go
+++ b/bind/bind.go
@@ -16,9 +16,8 @@
 	"bytes"
 	"go/format"
 	"go/token"
+	"go/types"
 	"io"
-
-	"golang.org/x/tools/go/types"
 )
 
 // GenJava generates a Java API from a Go package.
diff --git a/bind/bind_test.go b/bind/bind_test.go
index 7d685ba..b9a3dfd 100644
--- a/bind/bind_test.go
+++ b/bind/bind_test.go
@@ -6,6 +6,7 @@
 	"go/ast"
 	"go/parser"
 	"go/token"
+	"go/types"
 	"io/ioutil"
 	"log"
 	"os"
@@ -14,9 +15,6 @@
 	"runtime"
 	"strings"
 	"testing"
-
-	_ "golang.org/x/tools/go/gcimporter"
-	"golang.org/x/tools/go/types"
 )
 
 func init() {
diff --git a/bind/gengo.go b/bind/gengo.go
index 56a62ec..f503dc1 100644
--- a/bind/gengo.go
+++ b/bind/gengo.go
@@ -7,10 +7,9 @@
 import (
 	"fmt"
 	"go/token"
+	"go/types"
 	"log"
 	"strings"
-
-	"golang.org/x/tools/go/types"
 )
 
 type goGen struct {
diff --git a/bind/genjava.go b/bind/genjava.go
index 371955d..332b179 100644
--- a/bind/genjava.go
+++ b/bind/genjava.go
@@ -8,12 +8,11 @@
 	"bytes"
 	"fmt"
 	"go/token"
+	"go/types"
 	"io"
 	"regexp"
 	"unicode"
 	"unicode/utf8"
-
-	"golang.org/x/tools/go/types"
 )
 
 // TODO(crawshaw): disallow basic android java type names in exported symbols.
diff --git a/bind/genobjc.go b/bind/genobjc.go
index 81082b3..041ba50 100644
--- a/bind/genobjc.go
+++ b/bind/genobjc.go
@@ -7,7 +7,7 @@
 import (
 	"fmt"
 	"go/token"
-	"golang.org/x/tools/go/types"
+	"go/types"
 	"log"
 	"strings"
 	"unicode"
diff --git a/bind/seq.go b/bind/seq.go
index 1e41848..05e4d24 100644
--- a/bind/seq.go
+++ b/bind/seq.go
@@ -2,8 +2,7 @@
 
 import (
 	"fmt"
-
-	"golang.org/x/tools/go/types"
+	"go/types"
 )
 
 // seqType returns a string that can be used for reading and writing a
diff --git a/cmd/gobind/gen.go b/cmd/gobind/gen.go
index 817d5e6..377992a 100644
--- a/cmd/gobind/gen.go
+++ b/cmd/gobind/gen.go
@@ -5,43 +5,45 @@
 package main
 
 import (
+	"go/ast"
+	"go/build"
+	"go/parser"
+	"go/scanner"
+	"go/token"
+	"go/types"
 	"io"
 	"os"
 	"path/filepath"
 	"unicode"
 	"unicode/utf8"
 
-	"go/ast"
-	"go/build"
-	"go/parser"
-	"go/scanner"
-	"go/token"
-
 	"golang.org/x/mobile/bind"
-	"golang.org/x/tools/go/loader"
-	"golang.org/x/tools/go/types"
+	"golang.org/x/mobile/internal/loader"
 )
 
 func genPkg(pkg *build.Package) {
-	if len(pkg.CgoFiles) > 0 {
-		errorf("gobind: cannot use cgo-dependent package as service definition: %s", pkg.CgoFiles[0])
-		return
-	}
-
 	files := parseFiles(pkg.Dir, pkg.GoFiles)
 	if len(files) == 0 {
 		return // some error has been reported
 	}
 
 	conf := loader.Config{
-		Fset: fset,
+		Fset:        fset,
+		AllowErrors: true,
 	}
+	conf.TypeChecker.IgnoreFuncBodies = true
+	conf.TypeChecker.FakeImportC = true
+	conf.TypeChecker.DisableUnusedImportCheck = true
+	var tcErrs []error
 	conf.TypeChecker.Error = func(err error) {
-		errorf("%v", err)
+		tcErrs = append(tcErrs, err)
 	}
 	conf.CreateFromFiles(pkg.ImportPath, files...)
 	program, err := conf.Load()
 	if err != nil {
+		for _, err := range tcErrs {
+			errorf("%v", err)
+		}
 		errorf("%v", err)
 		return
 	}
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index 79c4971..585a9a9 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -12,6 +12,7 @@
 	"go/parser"
 	"go/scanner"
 	"go/token"
+	"go/types"
 	"io"
 	"io/ioutil"
 	"os"
@@ -19,8 +20,7 @@
 	"strings"
 
 	"golang.org/x/mobile/bind"
-	"golang.org/x/tools/go/loader"
-	"golang.org/x/tools/go/types"
+	"golang.org/x/mobile/internal/loader"
 )
 
 // ctx, pkg, tmpdir in build.go
@@ -226,10 +226,6 @@
 		return nil, fmt.Errorf("package %q: can only bind a library package", bindPkg.Name)
 	}
 
-	if len(bindPkg.CgoFiles) > 0 {
-		return nil, fmt.Errorf("cannot use cgo-dependent package as service definition: %s", bindPkg.CgoFiles[0])
-	}
-
 	fset := token.NewFileSet()
 
 	hasErr := false
@@ -255,15 +251,23 @@
 	}
 
 	conf := loader.Config{
-		Fset: fset,
+		Fset:        fset,
+		AllowErrors: true,
 	}
+	conf.TypeChecker.IgnoreFuncBodies = true
+	conf.TypeChecker.FakeImportC = true
+	conf.TypeChecker.DisableUnusedImportCheck = true
+	var tcErrs []error
 	conf.TypeChecker.Error = func(err error) {
-		fmt.Fprintln(os.Stderr, err)
+		tcErrs = append(tcErrs, err)
 	}
 
 	conf.CreateFromFiles(bindPkg.ImportPath, files...)
 	program, err := conf.Load()
 	if err != nil {
+		for _, err := range tcErrs {
+			fmt.Fprintln(os.Stderr, err)
+		}
 		return nil, err
 	}
 	b := &binder{
diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go
index dcdf0ed..9964d7e 100644
--- a/cmd/gomobile/bind_test.go
+++ b/cmd/gomobile/bind_test.go
@@ -63,5 +63,4 @@
 ln -s $GOPATH/src/golang.org/x/mobile/bind/java/Seq.java $WORK/android/src/main/java/go/Seq.java
 PWD=$WORK/android/src/main/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspath $ANDROID_HOME/platforms/android-22/android.jar *.java
 jar c -C $WORK/javac-output .
-rm -r -f "$WORK/javac-output"
 `))
diff --git a/internal/loader/README b/internal/loader/README
new file mode 100644
index 0000000..909783d
--- /dev/null
+++ b/internal/loader/README
@@ -0,0 +1,8 @@
+Vendored copy of golang.org/x/tools/go/loader.
+See vendor.bash.
+
+Modifications:
+
+- removed dependency on x/tools/astutil
+- removed dependency on x/tools/buildutil
+- uses Go 1.5's go/types package
diff --git a/internal/loader/cgo.go b/internal/loader/cgo.go
new file mode 100644
index 0000000..299e725
--- /dev/null
+++ b/internal/loader/cgo.go
@@ -0,0 +1,199 @@
+package loader
+
+// This file handles cgo preprocessing of files containing `import "C"`.
+//
+// DESIGN
+//
+// The approach taken is to run the cgo processor on the package's
+// CgoFiles and parse the output, faking the filenames of the
+// resulting ASTs so that the synthetic file containing the C types is
+// called "C" (e.g. "~/go/src/net/C") and the preprocessed files
+// have their original names (e.g. "~/go/src/net/cgo_unix.go"),
+// not the names of the actual temporary files.
+//
+// The advantage of this approach is its fidelity to 'go build'.  The
+// downside is that the token.Position.Offset for each AST node is
+// incorrect, being an offset within the temporary file.  Line numbers
+// should still be correct because of the //line comments.
+//
+// The logic of this file is mostly plundered from the 'go build'
+// tool, which also invokes the cgo preprocessor.
+//
+//
+// REJECTED ALTERNATIVE
+//
+// An alternative approach that we explored is to extend go/types'
+// Importer mechanism to provide the identity of the importing package
+// so that each time `import "C"` appears it resolves to a different
+// synthetic package containing just the objects needed in that case.
+// The loader would invoke cgo but parse only the cgo_types.go file
+// defining the package-level objects, discarding the other files
+// resulting from preprocessing.
+//
+// The benefit of this approach would have been that source-level
+// syntax information would correspond exactly to the original cgo
+// file, with no preprocessing involved, making source tools like
+// godoc, oracle, and eg happy.  However, the approach was rejected
+// due to the additional complexity it would impose on go/types.  (It
+// made for a beautiful demo, though.)
+//
+// cgo files, despite their *.go extension, are not legal Go source
+// files per the specification since they may refer to unexported
+// members of package "C" such as C.int.  Also, a function such as
+// C.getpwent has in effect two types, one matching its C type and one
+// which additionally returns (errno C.int).  The cgo preprocessor
+// uses name mangling to distinguish these two functions in the
+// processed code, but go/types would need to duplicate this logic in
+// its handling of function calls, analogous to the treatment of map
+// lookups in which y=m[k] and y,ok=m[k] are both legal.
+
+import (
+	"fmt"
+	"go/ast"
+	"go/build"
+	"go/parser"
+	"go/token"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+// processCgoFiles invokes the cgo preprocessor on bp.CgoFiles, parses
+// the output and returns the resulting ASTs.
+//
+func processCgoFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
+	tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
+	if err != nil {
+		return nil, err
+	}
+	defer os.RemoveAll(tmpdir)
+
+	pkgdir := bp.Dir
+	if DisplayPath != nil {
+		pkgdir = DisplayPath(pkgdir)
+	}
+
+	cgoFiles, cgoDisplayFiles, err := runCgo(bp, pkgdir, tmpdir)
+	if err != nil {
+		return nil, err
+	}
+	var files []*ast.File
+	for i := range cgoFiles {
+		rd, err := os.Open(cgoFiles[i])
+		if err != nil {
+			return nil, err
+		}
+		defer rd.Close()
+		display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
+		f, err := parser.ParseFile(fset, display, rd, mode)
+		if err != nil {
+			return nil, err
+		}
+		files = append(files, f)
+	}
+	return files, nil
+}
+
+var cgoRe = regexp.MustCompile(`[/\\:]`)
+
+// runCgo invokes the cgo preprocessor on bp.CgoFiles and returns two
+// lists of files: the resulting processed files (in temporary
+// directory tmpdir) and the corresponding names of the unprocessed files.
+//
+// runCgo is adapted from (*builder).cgo in
+// $GOROOT/src/cmd/go/build.go, but these features are unsupported:
+// pkg-config, Objective C, CGOPKGPATH, CGO_FLAGS.
+//
+func runCgo(bp *build.Package, pkgdir, tmpdir string) (files, displayFiles []string, err error) {
+	cgoCPPFLAGS, _, _, _ := cflags(bp, true)
+	_, cgoexeCFLAGS, _, _ := cflags(bp, false)
+
+	if len(bp.CgoPkgConfig) > 0 {
+		return nil, nil, fmt.Errorf("cgo pkg-config not supported")
+	}
+
+	// Allows including _cgo_export.h from .[ch] files in the package.
+	cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
+
+	// _cgo_gotypes.go (displayed "C") contains the type definitions.
+	files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
+	displayFiles = append(displayFiles, "C")
+	for _, fn := range bp.CgoFiles {
+		// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
+		f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
+		files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
+		displayFiles = append(displayFiles, fn)
+	}
+
+	var cgoflags []string
+	if bp.Goroot && bp.ImportPath == "runtime/cgo" {
+		cgoflags = append(cgoflags, "-import_runtime_cgo=false")
+	}
+	if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
+		cgoflags = append(cgoflags, "-import_syscall=false")
+	}
+
+	args := stringList(
+		"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
+		cgoCPPFLAGS, cgoexeCFLAGS, bp.CgoFiles,
+	)
+	if false {
+		log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
+	}
+	cmd := exec.Command(args[0], args[1:]...)
+	cmd.Dir = pkgdir
+	cmd.Stdout = os.Stderr
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
+	}
+
+	return files, displayFiles, nil
+}
+
+// -- unmodified from 'go build' ---------------------------------------
+
+// Return the flags to use when invoking the C or C++ compilers, or cgo.
+func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
+	var defaults string
+	if def {
+		defaults = "-g -O2"
+	}
+
+	cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
+	cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
+	cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
+	ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
+	return
+}
+
+// envList returns the value of the given environment variable broken
+// into fields, using the default value when the variable is empty.
+func envList(key, def string) []string {
+	v := os.Getenv(key)
+	if v == "" {
+		v = def
+	}
+	return strings.Fields(v)
+}
+
+// stringList's arguments should be a sequence of string or []string values.
+// stringList flattens them into a single []string.
+func stringList(args ...interface{}) []string {
+	var x []string
+	for _, arg := range args {
+		switch arg := arg.(type) {
+		case []string:
+			x = append(x, arg...)
+		case string:
+			x = append(x, arg)
+		default:
+			panic("stringList: invalid argument")
+		}
+	}
+	return x
+}
diff --git a/internal/loader/loader.go b/internal/loader/loader.go
new file mode 100644
index 0000000..11d06af
--- /dev/null
+++ b/internal/loader/loader.go
@@ -0,0 +1,856 @@
+// Copyright 2013 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.
+
+package loader
+
+// See doc.go for package documentation and implementation notes.
+
+import (
+	"errors"
+	"fmt"
+	"go/ast"
+	"go/build"
+	goimporter "go/importer"
+	"go/parser"
+	"go/token"
+	"go/types"
+	"os"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+)
+
+const trace = false // show timing info for type-checking
+
+// Config specifies the configuration for loading a whole program from
+// Go source code.
+// The zero value for Config is a ready-to-use default configuration.
+type Config struct {
+	// Fset is the file set for the parser to use when loading the
+	// program.  If nil, it may be lazily initialized by any
+	// method of Config.
+	Fset *token.FileSet
+
+	// ParserMode specifies the mode to be used by the parser when
+	// loading source packages.
+	ParserMode parser.Mode
+
+	// TypeChecker contains options relating to the type checker.
+	//
+	// The supplied IgnoreFuncBodies is not used; the effective
+	// value comes from the TypeCheckFuncBodies func below.
+	// The supplied Import function is not used either.
+	TypeChecker types.Config
+
+	// TypeCheckFuncBodies is a predicate over package import
+	// paths.  A package for which the predicate is false will
+	// have its package-level declarations type checked, but not
+	// its function bodies; this can be used to quickly load
+	// dependencies from source.  If nil, all func bodies are type
+	// checked.
+	TypeCheckFuncBodies func(string) bool
+
+	// If Build is non-nil, it is used to locate source packages.
+	// Otherwise &build.Default is used.
+	//
+	// By default, cgo is invoked to preprocess Go files that
+	// import the fake package "C".  This behaviour can be
+	// disabled by setting CGO_ENABLED=0 in the environment prior
+	// to startup, or by setting Build.CgoEnabled=false.
+	Build *build.Context
+
+	// The current directory, used for resolving relative package
+	// references such as "./go/loader".  If empty, os.Getwd will be
+	// used instead.
+	Cwd string
+
+	// If DisplayPath is non-nil, it is used to transform each
+	// file name obtained from Build.Import().  This can be used
+	// to prevent a virtualized build.Config's file names from
+	// leaking into the user interface.
+	DisplayPath func(path string) string
+
+	// If AllowErrors is true, Load will return a Program even
+	// if some of the its packages contained I/O, parser or type
+	// errors; such errors are accessible via PackageInfo.Errors.  If
+	// false, Load will fail if any package had an error.
+	AllowErrors bool
+
+	// CreatePkgs specifies a list of non-importable initial
+	// packages to create.  The resulting packages will appear in
+	// the corresponding elements of the Program.Created slice.
+	CreatePkgs []PkgSpec
+
+	// ImportPkgs specifies a set of initial packages to load from
+	// source.  The map keys are package import paths, used to
+	// locate the package relative to $GOROOT.
+	//
+	// The map value indicates whether to load tests.  If true, Load
+	// will add and type-check two lists of files to the package:
+	// non-test files followed by in-package *_test.go files.  In
+	// addition, it will append the external test package (if any)
+	// to Program.Created.
+	ImportPkgs map[string]bool
+
+	// FindPackage is called during Load to create the build.Package
+	// for a given import path.  If nil, a default implementation
+	// based on ctxt.Import is used.  A client may use this hook to
+	// adapt to a proprietary build system that does not follow the
+	// "go build" layout conventions, for example.
+	//
+	// It must be safe to call concurrently from multiple goroutines.
+	FindPackage func(ctxt *build.Context, importPath string) (*build.Package, error)
+}
+
+// A PkgSpec specifies a non-importable package to be created by Load.
+// Files are processed first, but typically only one of Files and
+// Filenames is provided.  The path needn't be globally unique.
+//
+type PkgSpec struct {
+	Path      string      // import path ("" => use package declaration)
+	Files     []*ast.File // ASTs of already-parsed files
+	Filenames []string    // names of files to be parsed
+}
+
+// A Program is a Go program loaded from source as specified by a Config.
+type Program struct {
+	Fset *token.FileSet // the file set for this program
+
+	// Created[i] contains the initial package whose ASTs or
+	// filenames were supplied by Config.CreatePkgs[i], followed by
+	// the external test package, if any, of each package in
+	// Config.ImportPkgs ordered by ImportPath.
+	Created []*PackageInfo
+
+	// Imported contains the initially imported packages,
+	// as specified by Config.ImportPkgs.
+	Imported map[string]*PackageInfo
+
+	// AllPackages contains the PackageInfo of every package
+	// encountered by Load: all initial packages and all
+	// dependencies, including incomplete ones.
+	AllPackages map[*types.Package]*PackageInfo
+
+	// importMap is the canonical mapping of import paths to
+	// packages.  It contains all Imported initial packages, but not
+	// Created ones, and all imported dependencies.
+	importMap map[string]*types.Package
+}
+
+// PackageInfo holds the ASTs and facts derived by the type-checker
+// for a single package.
+//
+// Not mutated once exposed via the API.
+//
+type PackageInfo struct {
+	Pkg                   *types.Package
+	Importable            bool        // true if 'import "Pkg.Path()"' would resolve to this
+	TransitivelyErrorFree bool        // true if Pkg and all its dependencies are free of errors
+	Files                 []*ast.File // syntax trees for the package's files
+	Errors                []error     // non-nil if the package had errors
+	types.Info                        // type-checker deductions.
+
+	checker   *types.Checker // transient type-checker state
+	errorFunc func(error)
+}
+
+func (info *PackageInfo) String() string { return info.Pkg.Path() }
+
+func (info *PackageInfo) appendError(err error) {
+	if info.errorFunc != nil {
+		info.errorFunc(err)
+	} else {
+		fmt.Fprintln(os.Stderr, err)
+	}
+	info.Errors = append(info.Errors, err)
+}
+
+func (conf *Config) fset() *token.FileSet {
+	if conf.Fset == nil {
+		conf.Fset = token.NewFileSet()
+	}
+	return conf.Fset
+}
+
+// ParseFile is a convenience function (intended for testing) that invokes
+// the parser using the Config's FileSet, which is initialized if nil.
+//
+// src specifies the parser input as a string, []byte, or io.Reader, and
+// filename is its apparent name.  If src is nil, the contents of
+// filename are read from the file system.
+//
+func (conf *Config) ParseFile(filename string, src interface{}) (*ast.File, error) {
+	// TODO(adonovan): use conf.build() etc like parseFiles does.
+	return parser.ParseFile(conf.fset(), filename, src, conf.ParserMode)
+}
+
+// FromArgsUsage is a partial usage message that applications calling
+// FromArgs may wish to include in their -help output.
+const FromArgsUsage = `
+<args> is a list of arguments denoting a set of initial packages.
+It may take one of two forms:
+
+1. A list of *.go source files.
+
+   All of the specified files are loaded, parsed and type-checked
+   as a single package.  All the files must belong to the same directory.
+
+2. A list of import paths, each denoting a package.
+
+   The package's directory is found relative to the $GOROOT and
+   $GOPATH using similar logic to 'go build', and the *.go files in
+   that directory are loaded, parsed and type-checked as a single
+   package.
+
+   In addition, all *_test.go files in the directory are then loaded
+   and parsed.  Those files whose package declaration equals that of
+   the non-*_test.go files are included in the primary package.  Test
+   files whose package declaration ends with "_test" are type-checked
+   as another package, the 'external' test package, so that a single
+   import path may denote two packages.  (Whether this behaviour is
+   enabled is tool-specific, and may depend on additional flags.)
+
+A '--' argument terminates the list of packages.
+`
+
+// FromArgs interprets args as a set of initial packages to load from
+// source and updates the configuration.  It returns the list of
+// unconsumed arguments.
+//
+// It is intended for use in command-line interfaces that require a
+// set of initial packages to be specified; see FromArgsUsage message
+// for details.
+//
+// Only superficial errors are reported at this stage; errors dependent
+// on I/O are detected during Load.
+//
+func (conf *Config) FromArgs(args []string, xtest bool) ([]string, error) {
+	var rest []string
+	for i, arg := range args {
+		if arg == "--" {
+			rest = args[i+1:]
+			args = args[:i]
+			break // consume "--" and return the remaining args
+		}
+	}
+
+	if len(args) > 0 && strings.HasSuffix(args[0], ".go") {
+		// Assume args is a list of a *.go files
+		// denoting a single ad hoc package.
+		for _, arg := range args {
+			if !strings.HasSuffix(arg, ".go") {
+				return nil, fmt.Errorf("named files must be .go files: %s", arg)
+			}
+		}
+		conf.CreateFromFilenames("", args...)
+	} else {
+		// Assume args are directories each denoting a
+		// package and (perhaps) an external test, iff xtest.
+		for _, arg := range args {
+			if xtest {
+				conf.ImportWithTests(arg)
+			} else {
+				conf.Import(arg)
+			}
+		}
+	}
+
+	return rest, nil
+}
+
+// CreateFromFilenames is a convenience function that adds
+// a conf.CreatePkgs entry to create a package of the specified *.go
+// files.
+//
+func (conf *Config) CreateFromFilenames(path string, filenames ...string) {
+	conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Filenames: filenames})
+}
+
+// CreateFromFiles is a convenience function that adds a conf.CreatePkgs
+// entry to create package of the specified path and parsed files.
+//
+func (conf *Config) CreateFromFiles(path string, files ...*ast.File) {
+	conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Files: files})
+}
+
+// ImportWithTests is a convenience function that adds path to
+// ImportPkgs, the set of initial source packages located relative to
+// $GOPATH.  The package will be augmented by any *_test.go files in
+// its directory that contain a "package x" (not "package x_test")
+// declaration.
+//
+// In addition, if any *_test.go files contain a "package x_test"
+// declaration, an additional package comprising just those files will
+// be added to CreatePkgs.
+//
+func (conf *Config) ImportWithTests(path string) { conf.addImport(path, true) }
+
+// Import is a convenience function that adds path to ImportPkgs, the
+// set of initial packages that will be imported from source.
+//
+func (conf *Config) Import(path string) { conf.addImport(path, false) }
+
+func (conf *Config) addImport(path string, tests bool) {
+	if path == "C" || path == "unsafe" {
+		return // ignore; not a real package
+	}
+	if conf.ImportPkgs == nil {
+		conf.ImportPkgs = make(map[string]bool)
+	}
+	conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests
+}
+
+// InitialPackages returns a new slice containing the set of initial
+// packages (Created + Imported) in unspecified order.
+//
+func (prog *Program) InitialPackages() []*PackageInfo {
+	infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported))
+	infos = append(infos, prog.Created...)
+	for _, info := range prog.Imported {
+		infos = append(infos, info)
+	}
+	return infos
+}
+
+// Package returns the ASTs and results of type checking for the
+// specified package.
+func (prog *Program) Package(path string) *PackageInfo {
+	if info, ok := prog.AllPackages[prog.importMap[path]]; ok {
+		return info
+	}
+	for _, info := range prog.Created {
+		if path == info.Pkg.Path() {
+			return info
+		}
+	}
+	return nil
+}
+
+// ---------- Implementation ----------
+
+// importer holds the working state of the algorithm.
+type importer struct {
+	conf  *Config   // the client configuration
+	start time.Time // for logging
+
+	progMu sync.Mutex // guards prog
+	prog   *Program   // the resulting program
+
+	importedMu sync.Mutex             // guards imported
+	imported   map[string]*importInfo // all imported packages (incl. failures) by import path
+
+	// import dependency graph: graph[x][y] => x imports y
+	//
+	// Since non-importable packages cannot be cyclic, we ignore
+	// their imports, thus we only need the subgraph over importable
+	// packages.  Nodes are identified by their import paths.
+	graphMu sync.Mutex
+	graph   map[string]map[string]bool
+}
+
+// importInfo tracks the success or failure of a single import.
+//
+// Upon completion, exactly one of info and err is non-nil:
+// info on successful creation of a package, err otherwise.
+// A successful package may still contain type errors.
+//
+type importInfo struct {
+	path     string       // import path
+	mu       sync.Mutex   // guards the following fields prior to completion
+	info     *PackageInfo // results of typechecking (including errors)
+	err      error        // reason for failure to create a package
+	complete sync.Cond    // complete condition is that one of info, err is non-nil.
+}
+
+// awaitCompletion blocks until ii is complete,
+// i.e. the info and err fields are safe to inspect without a lock.
+// It is concurrency-safe and idempotent.
+func (ii *importInfo) awaitCompletion() {
+	ii.mu.Lock()
+	for ii.info == nil && ii.err == nil {
+		ii.complete.Wait()
+	}
+	ii.mu.Unlock()
+}
+
+// Complete marks ii as complete.
+// Its info and err fields will not be subsequently updated.
+func (ii *importInfo) Complete(info *PackageInfo, err error) {
+	ii.mu.Lock()
+	ii.info = info
+	ii.err = err
+	ii.complete.Broadcast()
+	ii.mu.Unlock()
+}
+
+// Load creates the initial packages specified by conf.{Create,Import}Pkgs,
+// loading their dependencies packages as needed.
+//
+// On success, Load returns a Program containing a PackageInfo for
+// each package.  On failure, it returns an error.
+//
+// If AllowErrors is true, Load will return a Program even if some
+// packages contained I/O, parser or type errors, or if dependencies
+// were missing.  (Such errors are accessible via PackageInfo.Errors.  If
+// false, Load will fail if any package had an error.
+//
+// It is an error if no packages were loaded.
+//
+func (conf *Config) Load() (*Program, error) {
+	// Create a simple default error handler for parse/type errors.
+	if conf.TypeChecker.Error == nil {
+		conf.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
+	}
+
+	// Set default working directory for relative package references.
+	if conf.Cwd == "" {
+		var err error
+		conf.Cwd, err = os.Getwd()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// Install default FindPackage hook using go/build logic.
+	if conf.FindPackage == nil {
+		conf.FindPackage = func(ctxt *build.Context, path string) (*build.Package, error) {
+			// TODO(adonovan): cache calls to build.Import
+			// so we don't do it three times per test package.
+			bp, err := ctxt.Import(path, conf.Cwd, 0)
+			if _, ok := err.(*build.NoGoError); ok {
+				return bp, nil // empty directory is not an error
+			}
+			return bp, err
+		}
+	}
+
+	prog := &Program{
+		Fset:        conf.fset(),
+		Imported:    make(map[string]*PackageInfo),
+		importMap:   make(map[string]*types.Package),
+		AllPackages: make(map[*types.Package]*PackageInfo),
+	}
+
+	imp := importer{
+		conf:     conf,
+		prog:     prog,
+		imported: make(map[string]*importInfo),
+		start:    time.Now(),
+		graph:    make(map[string]map[string]bool),
+	}
+
+	// -- loading proper (concurrent phase) --------------------------------
+
+	var errpkgs []string // packages that contained errors
+
+	// Load the initially imported packages and their dependencies,
+	// in parallel.
+	for _, ii := range imp.loadAll("", conf.ImportPkgs) {
+		if ii.err != nil {
+			conf.TypeChecker.Error(ii.err) // failed to create package
+			errpkgs = append(errpkgs, ii.path)
+			continue
+		}
+		prog.Imported[ii.info.Pkg.Path()] = ii.info
+	}
+
+	// Augment the designated initial packages by their tests.
+	// Dependencies are loaded in parallel.
+	var xtestPkgs []*build.Package
+	for path, augment := range conf.ImportPkgs {
+		if !augment {
+			continue
+		}
+
+		bp, err := conf.FindPackage(conf.build(), path)
+		if err != nil {
+			// Package not found, or can't even parse package declaration.
+			// Already reported by previous loop; ignore it.
+			continue
+		}
+
+		// Needs external test package?
+		if len(bp.XTestGoFiles) > 0 {
+			xtestPkgs = append(xtestPkgs, bp)
+		}
+
+		imp.importedMu.Lock()           // (unnecessary, we're sequential here)
+		info := imp.imported[path].info // must be non-nil, see above
+		imp.importedMu.Unlock()
+
+		// Parse the in-package test files.
+		files, errs := imp.conf.parsePackageFiles(bp, 't')
+		for _, err := range errs {
+			info.appendError(err)
+		}
+
+		// The test files augmenting package P cannot be imported,
+		// but may import packages that import P,
+		// so we must disable the cycle check.
+		imp.addFiles(info, files, false)
+	}
+
+	createPkg := func(path string, files []*ast.File, errs []error) {
+		info := imp.newPackageInfo(path)
+		for _, err := range errs {
+			info.appendError(err)
+		}
+
+		// Ad hoc packages are non-importable,
+		// so no cycle check is needed.
+		// addFiles loads dependencies in parallel.
+		imp.addFiles(info, files, false)
+		prog.Created = append(prog.Created, info)
+	}
+
+	// Create packages specified by conf.CreatePkgs.
+	for _, cp := range conf.CreatePkgs {
+		files, errs := parseFiles(conf.fset(), conf.build(), nil, ".", cp.Filenames, conf.ParserMode)
+		files = append(files, cp.Files...)
+
+		path := cp.Path
+		if path == "" {
+			if len(files) > 0 {
+				path = files[0].Name.Name
+			} else {
+				path = "(unnamed)"
+			}
+		}
+		createPkg(path, files, errs)
+	}
+
+	// Create external test packages.
+	sort.Sort(byImportPath(xtestPkgs))
+	for _, bp := range xtestPkgs {
+		files, errs := imp.conf.parsePackageFiles(bp, 'x')
+		createPkg(bp.ImportPath+"_test", files, errs)
+	}
+
+	// -- finishing up (sequential) ----------------------------------------
+
+	if len(prog.Imported)+len(prog.Created) == 0 {
+		return nil, errors.New("no initial packages were loaded")
+	}
+
+	// Create infos for indirectly imported packages.
+	// e.g. incomplete packages without syntax, loaded from export data.
+	for _, obj := range prog.importMap {
+		info := prog.AllPackages[obj]
+		if info == nil {
+			prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true}
+		} else {
+			// finished
+			info.checker = nil
+			info.errorFunc = nil
+		}
+	}
+
+	if !conf.AllowErrors {
+		// Report errors in indirectly imported packages.
+		for _, info := range prog.AllPackages {
+			if len(info.Errors) > 0 {
+				errpkgs = append(errpkgs, info.Pkg.Path())
+			}
+		}
+		if errpkgs != nil {
+			var more string
+			if len(errpkgs) > 3 {
+				more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
+				errpkgs = errpkgs[:3]
+			}
+			return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
+				strings.Join(errpkgs, ", "), more)
+		}
+	}
+
+	markErrorFreePackages(prog.AllPackages)
+
+	return prog, nil
+}
+
+type byImportPath []*build.Package
+
+func (b byImportPath) Len() int           { return len(b) }
+func (b byImportPath) Less(i, j int) bool { return b[i].ImportPath < b[j].ImportPath }
+func (b byImportPath) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
+
+// markErrorFreePackages sets the TransitivelyErrorFree flag on all
+// applicable packages.
+func markErrorFreePackages(allPackages map[*types.Package]*PackageInfo) {
+	// Build the transpose of the import graph.
+	importedBy := make(map[*types.Package]map[*types.Package]bool)
+	for P := range allPackages {
+		for _, Q := range P.Imports() {
+			clients, ok := importedBy[Q]
+			if !ok {
+				clients = make(map[*types.Package]bool)
+				importedBy[Q] = clients
+			}
+			clients[P] = true
+		}
+	}
+
+	// Find all packages reachable from some error package.
+	reachable := make(map[*types.Package]bool)
+	var visit func(*types.Package)
+	visit = func(p *types.Package) {
+		if !reachable[p] {
+			reachable[p] = true
+			for q := range importedBy[p] {
+				visit(q)
+			}
+		}
+	}
+	for _, info := range allPackages {
+		if len(info.Errors) > 0 {
+			visit(info.Pkg)
+		}
+	}
+
+	// Mark the others as "transitively error-free".
+	for _, info := range allPackages {
+		if !reachable[info.Pkg] {
+			info.TransitivelyErrorFree = true
+		}
+	}
+}
+
+// build returns the effective build context.
+func (conf *Config) build() *build.Context {
+	if conf.Build != nil {
+		return conf.Build
+	}
+	return &build.Default
+}
+
+// parsePackageFiles enumerates the files belonging to package path,
+// then loads, parses and returns them, plus a list of I/O or parse
+// errors that were encountered.
+//
+// 'which' indicates which files to include:
+//    'g': include non-test *.go source files (GoFiles + processed CgoFiles)
+//    't': include in-package *_test.go source files (TestGoFiles)
+//    'x': include external *_test.go source files. (XTestGoFiles)
+//
+func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.File, []error) {
+	var filenames []string
+	switch which {
+	case 'g':
+		filenames = bp.GoFiles
+	case 't':
+		filenames = bp.TestGoFiles
+	case 'x':
+		filenames = bp.XTestGoFiles
+	default:
+		panic(which)
+	}
+
+	files, errs := parseFiles(conf.fset(), conf.build(), conf.DisplayPath, bp.Dir, filenames, conf.ParserMode)
+
+	// Preprocess CgoFiles and parse the outputs (sequentially).
+	if which == 'g' && bp.CgoFiles != nil {
+		cgofiles, err := processCgoFiles(bp, conf.fset(), conf.DisplayPath, conf.ParserMode)
+		if err != nil {
+			errs = append(errs, err)
+		} else {
+			files = append(files, cgofiles...)
+		}
+	}
+
+	return files, errs
+}
+
+// loadAll loads, parses, and type-checks the specified packages in
+// parallel and returns their completed importInfos in unspecified order.
+//
+// fromPath is the import path of the importing package, if it is
+// importable, "" otherwise.  It is used for cycle detection.
+//
+func (imp *importer) loadAll(fromPath string, paths map[string]bool) []*importInfo {
+	result := make([]*importInfo, 0, len(paths))
+	for path := range paths {
+		result = append(result, imp.startLoad(path))
+	}
+
+	if fromPath != "" {
+		// We're loading a set of imports.
+		//
+		// We must record graph edges from the importing package
+		// to its dependencies, and check for cycles.
+		imp.graphMu.Lock()
+		deps, ok := imp.graph[fromPath]
+		if !ok {
+			deps = make(map[string]bool)
+			imp.graph[fromPath] = deps
+		}
+		for path := range paths {
+			deps[path] = true
+		}
+		imp.graphMu.Unlock()
+	}
+
+	for _, ii := range result {
+		if fromPath != "" {
+			if cycle := imp.findPath(ii.path, fromPath); cycle != nil {
+				// Cycle-forming import: we must not await its
+				// completion since it would deadlock.
+				//
+				// We don't record the error in ii since
+				// the error is really associated with the
+				// cycle-forming edge, not the package itself.
+				// (Also it would complicate the
+				// invariants of importPath completion.)
+				if trace {
+					fmt.Fprintln(os.Stderr, "import cycle: %q", cycle)
+				}
+				continue
+			}
+		}
+		ii.awaitCompletion()
+
+	}
+	return result
+}
+
+// findPath returns an arbitrary path from 'from' to 'to' in the import
+// graph, or nil if there was none.
+func (imp *importer) findPath(from, to string) []string {
+	imp.graphMu.Lock()
+	defer imp.graphMu.Unlock()
+
+	seen := make(map[string]bool)
+	var search func(stack []string, importPath string) []string
+	search = func(stack []string, importPath string) []string {
+		if !seen[importPath] {
+			seen[importPath] = true
+			stack = append(stack, importPath)
+			if importPath == to {
+				return stack
+			}
+			for x := range imp.graph[importPath] {
+				if p := search(stack, x); p != nil {
+					return p
+				}
+			}
+		}
+		return nil
+	}
+	return search(make([]string, 0, 20), from)
+}
+
+// startLoad initiates the loading, parsing and type-checking of the
+// specified package and its dependencies, if it has not already begun.
+//
+// It returns an importInfo, not necessarily in a completed state.  The
+// caller must call awaitCompletion() before accessing its info and err
+// fields.
+//
+// startLoad is concurrency-safe and idempotent.
+//
+// Precondition: path != "unsafe".
+//
+func (imp *importer) startLoad(path string) *importInfo {
+	imp.importedMu.Lock()
+	ii, ok := imp.imported[path]
+	if !ok {
+		ii = &importInfo{path: path}
+		ii.complete.L = &ii.mu
+		imp.imported[path] = ii
+		go func() {
+			ii.Complete(imp.load(path))
+		}()
+	}
+	imp.importedMu.Unlock()
+
+	return ii
+}
+
+// load implements package loading by parsing Go source files
+// located by go/build.
+//
+func (imp *importer) load(path string) (*PackageInfo, error) {
+	bp, err := imp.conf.FindPackage(imp.conf.build(), path)
+	if err != nil {
+		return nil, err // package not found
+	}
+	info := imp.newPackageInfo(bp.ImportPath)
+	info.Importable = true
+	files, errs := imp.conf.parsePackageFiles(bp, 'g')
+	for _, err := range errs {
+		info.appendError(err)
+	}
+
+	imp.addFiles(info, files, true)
+
+	imp.progMu.Lock()
+	imp.prog.importMap[path] = info.Pkg
+	imp.progMu.Unlock()
+
+	return info, nil
+}
+
+// addFiles adds and type-checks the specified files to info, loading
+// their dependencies if needed.  The order of files determines the
+// package initialization order.  It may be called multiple times on the
+// same package.  Errors are appended to the info.Errors field.
+//
+// cycleCheck determines whether the imports within files create
+// dependency edges that should be checked for potential cycles.
+//
+func (imp *importer) addFiles(info *PackageInfo, files []*ast.File, cycleCheck bool) {
+	info.Files = append(info.Files, files...)
+
+	// Ensure the dependencies are loaded, in parallel.
+	var fromPath string
+	if cycleCheck {
+		fromPath = info.Pkg.Path()
+	}
+	imp.loadAll(fromPath, scanImports(files))
+
+	if trace {
+		fmt.Fprintf(os.Stderr, "%s: start %q (%d)\n",
+			time.Since(imp.start), info.Pkg.Path(), len(files))
+	}
+
+	// Ignore the returned (first) error since we
+	// already collect them all in the PackageInfo.
+	info.checker.Files(files)
+
+	if trace {
+		fmt.Fprintf(os.Stderr, "%s: stop %q\n",
+			time.Since(imp.start), info.Pkg.Path())
+	}
+}
+
+func (imp *importer) newPackageInfo(path string) *PackageInfo {
+	pkg := types.NewPackage(path, "")
+	info := &PackageInfo{
+		Pkg: pkg,
+		Info: types.Info{
+			Types:      make(map[ast.Expr]types.TypeAndValue),
+			Defs:       make(map[*ast.Ident]types.Object),
+			Uses:       make(map[*ast.Ident]types.Object),
+			Implicits:  make(map[ast.Node]types.Object),
+			Scopes:     make(map[ast.Node]*types.Scope),
+			Selections: make(map[*ast.SelectorExpr]*types.Selection),
+		},
+		errorFunc: imp.conf.TypeChecker.Error,
+	}
+
+	// Copy the types.Config so we can vary it across PackageInfos.
+	tc := imp.conf.TypeChecker
+	tc.IgnoreFuncBodies = false
+	if f := imp.conf.TypeCheckFuncBodies; f != nil {
+		tc.IgnoreFuncBodies = !f(path)
+	}
+	tc.Importer = goimporter.Default()
+	tc.Error = info.appendError // appendError wraps the user's Error function
+
+	info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info)
+	imp.progMu.Lock()
+	imp.prog.AllPackages[pkg] = info
+	imp.progMu.Unlock()
+	return info
+}
diff --git a/internal/loader/util.go b/internal/loader/util.go
new file mode 100644
index 0000000..b651fc6
--- /dev/null
+++ b/internal/loader/util.go
@@ -0,0 +1,132 @@
+// Copyright 2013 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.
+
+package loader
+
+import (
+	"go/ast"
+	"go/build"
+	"go/parser"
+	"go/token"
+	"io"
+	"os"
+	"path/filepath"
+	"strconv"
+	"sync"
+)
+
+// We use a counting semaphore to limit
+// the number of parallel I/O calls per process.
+var sema = make(chan bool, 10)
+
+// parseFiles parses the Go source files within directory dir and
+// returns the ASTs of the ones that could be at least partially parsed,
+// along with a list of I/O and parse errors encountered.
+//
+// I/O is done via ctxt, which may specify a virtual file system.
+// displayPath is used to transform the filenames attached to the ASTs.
+//
+func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) {
+	isAbsPath := ctxt.IsAbsPath
+	if isAbsPath == nil {
+		isAbsPath = filepath.IsAbs
+	}
+	joinPath := ctxt.JoinPath
+	if joinPath == nil {
+		joinPath = filepath.Join
+	}
+
+	if displayPath == nil {
+		displayPath = func(path string) string { return path }
+	}
+	var wg sync.WaitGroup
+	n := len(files)
+	parsed := make([]*ast.File, n)
+	errors := make([]error, n)
+	for i, file := range files {
+		if !isAbsPath(file) {
+			file = joinPath(dir, file)
+		}
+		wg.Add(1)
+		go func(i int, file string) {
+			sema <- true // wait
+			defer func() {
+				wg.Done()
+				<-sema // signal
+			}()
+			var rd io.ReadCloser
+			var err error
+			if ctxt.OpenFile != nil {
+				rd, err = ctxt.OpenFile(file)
+			} else {
+				rd, err = os.Open(file)
+			}
+			if err != nil {
+				errors[i] = err // open failed
+				return
+			}
+
+			// ParseFile may return both an AST and an error.
+			parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode)
+			rd.Close()
+		}(i, file)
+	}
+	wg.Wait()
+
+	// Eliminate nils, preserving order.
+	var o int
+	for _, f := range parsed {
+		if f != nil {
+			parsed[o] = f
+			o++
+		}
+	}
+	parsed = parsed[:o]
+
+	o = 0
+	for _, err := range errors {
+		if err != nil {
+			errors[o] = err
+			o++
+		}
+	}
+	errors = errors[:o]
+
+	return parsed, errors
+}
+
+// scanImports returns the set of all package import paths from all
+// import specs in the specified files.
+func scanImports(files []*ast.File) map[string]bool {
+	imports := make(map[string]bool)
+	for _, f := range files {
+		for _, decl := range f.Decls {
+			if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
+				for _, spec := range decl.Specs {
+					spec := spec.(*ast.ImportSpec)
+
+					// NB: do not assume the program is well-formed!
+					path, err := strconv.Unquote(spec.Path.Value)
+					if err != nil {
+						continue // quietly ignore the error
+					}
+					if path == "C" || path == "unsafe" {
+						continue // skip pseudo packages
+					}
+					imports[path] = true
+				}
+			}
+		}
+	}
+	return imports
+}
+
+// ---------- Internal helpers ----------
+
+// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
+func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
+	p := int(pos)
+	base := f.Base()
+	return base <= p && p < base+f.Size()
+}
diff --git a/internal/loader/vendor.bash b/internal/loader/vendor.bash
new file mode 100755
index 0000000..a5bdffd
--- /dev/null
+++ b/internal/loader/vendor.bash
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+# Copyright 2015 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.
+
+# Run this script to obtain an up-to-date vendored version of go/loader.
+
+set -e
+
+if [ ! -f vendor.bash ]; then
+	echo 'vendor.bash must be run from the loader directory' 1>&2
+	exit 1
+fi
+
+rm -f *.go
+for f in cgo.go loader.go util.go; do
+	cp $GOPATH/src/golang.org/x/tools/go/loader/$f .
+done
+
+patch -p1 < vendor.patch
diff --git a/internal/loader/vendor.patch b/internal/loader/vendor.patch
new file mode 100644
index 0000000..b4f0c5f
--- /dev/null
+++ b/internal/loader/vendor.patch
@@ -0,0 +1,167 @@
+diff --git a/loader.go b/loader.go
+index 5555faa..11d06af 100644
+--- a/loader.go
++++ b/loader.go
+@@ -11,16 +11,15 @@ import (
+ 	"fmt"
+ 	"go/ast"
+ 	"go/build"
++	goimporter "go/importer"
+ 	"go/parser"
+ 	"go/token"
++	"go/types"
+ 	"os"
+ 	"sort"
+ 	"strings"
+ 	"sync"
+ 	"time"
+-
+-	"golang.org/x/tools/go/ast/astutil"
+-	"golang.org/x/tools/go/types"
+ )
+ 
+ const trace = false // show timing info for type-checking
+@@ -303,33 +302,6 @@ func (conf *Config) addImport(path string, tests bool) {
+ 	conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests
+ }
+ 
+-// PathEnclosingInterval returns the PackageInfo and ast.Node that
+-// contain source interval [start, end), and all the node's ancestors
+-// up to the AST root.  It searches all ast.Files of all packages in prog.
+-// exact is defined as for astutil.PathEnclosingInterval.
+-//
+-// The zero value is returned if not found.
+-//
+-func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
+-	for _, info := range prog.AllPackages {
+-		for _, f := range info.Files {
+-			if f.Pos() == token.NoPos {
+-				// This can happen if the parser saw
+-				// too many errors and bailed out.
+-				// (Use parser.AllErrors to prevent that.)
+-				continue
+-			}
+-			if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) {
+-				continue
+-			}
+-			if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil {
+-				return info, path, exact
+-			}
+-		}
+-	}
+-	return nil, nil, false
+-}
+-
+ // InitialPackages returns a new slice containing the set of initial
+ // packages (Created + Imported) in unspecified order.
+ //
+@@ -690,55 +662,6 @@ func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.Fil
+ 	return files, errs
+ }
+ 
+-// doImport imports the package denoted by path.
+-// It implements the types.Importer signature.
+-//
+-// imports is the type-checker's package canonicalization map.
+-//
+-// It returns an error if a package could not be created
+-// (e.g. go/build or parse error), but type errors are reported via
+-// the types.Config.Error callback (the first of which is also saved
+-// in the package's PackageInfo).
+-//
+-// Idempotent.
+-//
+-func (imp *importer) doImport(from *PackageInfo, to string) (*types.Package, error) {
+-	// Package unsafe is handled specially, and has no PackageInfo.
+-	// TODO(adonovan): move this check into go/types?
+-	if to == "unsafe" {
+-		return types.Unsafe, nil
+-	}
+-	if to == "C" {
+-		// This should be unreachable, but ad hoc packages are
+-		// not currently subject to cgo preprocessing.
+-		// See https://github.com/golang/go/issues/11627.
+-		return nil, fmt.Errorf(`the loader doesn't cgo-process ad hoc packages like %q; see Go issue 11627`,
+-			from.Pkg.Path())
+-	}
+-
+-	imp.importedMu.Lock()
+-	ii := imp.imported[to]
+-	imp.importedMu.Unlock()
+-	if ii == nil {
+-		panic("internal error: unexpected import: " + to)
+-	}
+-	if ii.err != nil {
+-		return nil, ii.err
+-	}
+-	if ii.info != nil {
+-		return ii.info.Pkg, nil
+-	}
+-
+-	// Import of incomplete package: this indicates a cycle.
+-	fromPath := from.Pkg.Path()
+-	if cycle := imp.findPath(to, fromPath); cycle != nil {
+-		cycle = append([]string{fromPath}, cycle...)
+-		return nil, fmt.Errorf("import cycle: %s", strings.Join(cycle, " -> "))
+-	}
+-
+-	panic("internal error: import of incomplete (yet acyclic) package: " + fromPath)
+-}
+-
+ // loadAll loads, parses, and type-checks the specified packages in
+ // parallel and returns their completed importInfos in unspecified order.
+ //
+@@ -922,9 +845,7 @@ func (imp *importer) newPackageInfo(path string) *PackageInfo {
+ 	if f := imp.conf.TypeCheckFuncBodies; f != nil {
+ 		tc.IgnoreFuncBodies = !f(path)
+ 	}
+-	tc.Import = func(_ map[string]*types.Package, to string) (*types.Package, error) {
+-		return imp.doImport(info, to)
+-	}
++	tc.Importer = goimporter.Default()
+ 	tc.Error = info.appendError // appendError wraps the user's Error function
+ 
+ 	info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info)
+diff --git a/util.go b/util.go
+index 0404e99..b651fc6 100644
+--- a/util.go
++++ b/util.go
+@@ -11,10 +11,9 @@ import (
+ 	"go/token"
+ 	"io"
+ 	"os"
++	"path/filepath"
+ 	"strconv"
+ 	"sync"
+-
+-	"golang.org/x/tools/go/buildutil"
+ )
+ 
+ // We use a counting semaphore to limit
+@@ -29,6 +28,15 @@ var sema = make(chan bool, 10)
+ // displayPath is used to transform the filenames attached to the ASTs.
+ //
+ func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) {
++	isAbsPath := ctxt.IsAbsPath
++	if isAbsPath == nil {
++		isAbsPath = filepath.IsAbs
++	}
++	joinPath := ctxt.JoinPath
++	if joinPath == nil {
++		joinPath = filepath.Join
++	}
++
+ 	if displayPath == nil {
+ 		displayPath = func(path string) string { return path }
+ 	}
+@@ -37,8 +45,8 @@ func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(strin
+ 	parsed := make([]*ast.File, n)
+ 	errors := make([]error, n)
+ 	for i, file := range files {
+-		if !buildutil.IsAbsPath(ctxt, file) {
+-			file = buildutil.JoinPath(ctxt, dir, file)
++		if !isAbsPath(file) {
++			file = joinPath(dir, file)
+ 		}
+ 		wg.Add(1)
+ 		go func(i int, file string) {