--- /dev/null
+// 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.
+
+package main
+
+import (
+       "go/build"
+       "os"
+       "path"
+       "path/filepath"
+       "strings"
+)
+
+// Dirs is a structure for scanning the directory tree.
+// Its Next method returns the next Go source directory it finds.
+// Although it can be used to scan the tree multiple times, it
+// only walks the tree once, caching the data it finds.
+type Dirs struct {
+       scan   chan string // directories generated by walk.
+       paths  []string    // Cache of known paths.
+       offset int         // Counter for Next.
+}
+
+var dirs Dirs
+
+func init() {
+       dirs.paths = make([]string, 0, 1000)
+       dirs.scan = make(chan string)
+       go dirs.walk()
+}
+
+// Reset puts the scan back at the beginning.
+func (d *Dirs) Reset() {
+       d.offset = 0
+}
+
+// Next returns the next directory in the scan. The boolean
+// is false when the scan is done.
+func (d *Dirs) Next() (string, bool) {
+       if d.offset < len(d.paths) {
+               path := d.paths[d.offset]
+               d.offset++
+               return path, true
+       }
+       path, ok := <-d.scan
+       if !ok {
+               return "", false
+       }
+       d.paths = append(d.paths, path)
+       d.offset++
+       return path, ok
+}
+
+// walk walks the trees in GOROOT and GOPATH.
+func (d *Dirs) walk() {
+       d.walkRoot(build.Default.GOROOT)
+       for _, root := range splitGopath() {
+               d.walkRoot(root)
+       }
+       close(d.scan)
+}
+
+// walkRoot walks a single directory. Each Go source directory it finds is
+// delivered on d.scan.
+func (d *Dirs) walkRoot(root string) {
+       root = path.Join(root, "src")
+       slashDot := string(filepath.Separator) + "."
+       // We put a slash on the pkg so can use simple string comparison below
+       // yet avoid inadvertent matches, like /foobar matching bar.
+
+       visit := func(pathName string, f os.FileInfo, err error) error {
+               if err != nil {
+                       return nil
+               }
+               // One package per directory. Ignore the files themselves.
+               if !f.IsDir() {
+                       return nil
+               }
+               // No .git or other dot nonsense please.
+               if strings.Contains(pathName, slashDot) {
+                       return filepath.SkipDir
+               }
+               // Does the directory contain any Go files? If so, it's a candidate.
+               if hasGoFiles(pathName) {
+                       d.scan <- pathName
+                       return nil
+               }
+               return nil
+       }
+
+       filepath.Walk(root, visit)
+}
+
+// hasGoFiles tests whether the directory contains at least one file with ".go"
+// extension
+func hasGoFiles(path string) bool {
+       dir, err := os.Open(path)
+       if err != nil {
+               // ignore unreadable directories
+               return false
+       }
+       defer dir.Close()
+
+       names, err := dir.Readdirnames(0)
+       if err != nil {
+               // ignore unreadable directories
+               return false
+       }
+
+       for _, name := range names {
+               if strings.HasSuffix(name, ".go") {
+                       return true
+               }
+       }
+
+       return false
+}
 
 import (
        "bytes"
        "flag"
-       "os"
-       "os/exec"
        "regexp"
        "runtime"
+       "strings"
        "testing"
 )
 
+func maybeSkip(t *testing.T) {
+       if strings.HasPrefix(runtime.GOOS, "nacl") {
+               t.Skip("nacl does not have a full file tree")
+       }
+       if runtime.GOOS == "darwin" && strings.HasPrefix(runtime.GOARCH, "arm") {
+               t.Skip("darwin/arm does not have a full file tree")
+       }
+}
+
 const (
        dataDir = "testdata"
        binary  = "testdoc"
 }
 
 func TestDoc(t *testing.T) {
-       if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
-               t.Skip("TODO: on darwin/arm, test fails: no such package cmd/doc/testdata")
-       }
+       maybeSkip(t)
        for _, test := range tests {
                var b bytes.Buffer
                var flagSet flag.FlagSet
        }
 }
 
-// run runs the command, but calls t.Fatal if there is an error.
-func run(c *exec.Cmd, t *testing.T) []byte {
-       output, err := c.CombinedOutput()
-       if err != nil {
-               os.Stdout.Write(output)
-               t.Fatal(err)
+// Test the code to try multiple packages. Our test case is
+//     go doc rand.Float64
+// This needs to find math/rand.Float64; however crypto/rand, which doesn't
+// have the symbol, usually appears first in the directory listing.
+func TestMultiplePackages(t *testing.T) {
+       if testing.Short() {
+               t.Skip("scanning file system takes too long")
+       }
+       maybeSkip(t)
+       var b bytes.Buffer // We don't care about the output.
+       // Make sure crypto/rand does not have the symbol.
+       {
+               var flagSet flag.FlagSet
+               err := do(&b, &flagSet, []string{"crypto/rand.float64"})
+               if err == nil {
+                       t.Errorf("expected error from crypto/rand.float64")
+               } else if !strings.Contains(err.Error(), "no symbol float64") {
+                       t.Errorf("unexpected error %q from crypto/rand.float64", err)
+               }
+       }
+       // Make sure math/rand does have the symbol.
+       {
+               var flagSet flag.FlagSet
+               err := do(&b, &flagSet, []string{"math/rand.float64"})
+               if err != nil {
+                       t.Errorf("unexpected error %q from math/rand.float64", err)
+               }
+       }
+       // Try the shorthand.
+       {
+               var flagSet flag.FlagSet
+               err := do(&b, &flagSet, []string{"rand.float64"})
+               if err != nil {
+                       t.Errorf("unexpected error %q from rand.float64", err)
+               }
+       }
+       // Now try a missing symbol. We should see both packages in the error.
+       {
+               var flagSet flag.FlagSet
+               err := do(&b, &flagSet, []string{"rand.doesnotexit"})
+               if err == nil {
+                       t.Errorf("expected error from rand.doesnotexit")
+               } else {
+                       errStr := err.Error()
+                       if !strings.Contains(errStr, "no symbol") {
+                               t.Errorf("error %q should contain 'no symbol", errStr)
+                       }
+                       if !strings.Contains(errStr, "crypto/rand") {
+                               t.Errorf("error %q should contain crypto/rand", errStr)
+                       }
+                       if !strings.Contains(errStr, "math/rand") {
+                               t.Errorf("error %q should contain math/rand", errStr)
+                       }
+               }
        }
-       return output
 }
 
 package main
 
 import (
+       "bytes"
        "flag"
        "fmt"
        "go/build"
        "io"
        "log"
        "os"
-       "path"
        "path/filepath"
        "strings"
        "unicode"
        flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)")
        flagSet.BoolVar(&showCmd, "cmd", false, "show symbols with package docs even if package is a command")
        flagSet.Parse(args)
-       buildPackage, userPath, symbol := parseArgs(flagSet.Args())
-       symbol, method := parseSymbol(symbol)
-       pkg := parsePackage(writer, buildPackage, userPath)
+       var paths []string
+       var symbol, method string
+       // Loop until something is printed.
+       dirs.Reset()
+       for i := 0; ; i++ {
+               buildPackage, userPath, sym, more := parseArgs(flagSet.Args())
+               if i > 0 && !more { // Ignore the "more" bit on the first iteration.
+                       return failMessage(paths, symbol, method)
+               }
+               symbol, method = parseSymbol(sym)
+               pkg := parsePackage(writer, buildPackage, userPath)
+               paths = append(paths, pkg.prettyPath())
 
-       defer func() {
-               pkg.flush()
-               e := recover()
-               if e == nil {
-                       return
+               defer func() {
+                       pkg.flush()
+                       e := recover()
+                       if e == nil {
+                               return
+                       }
+                       pkgError, ok := e.(PackageError)
+                       if ok {
+                               err = pkgError
+                               return
+                       }
+                       panic(e)
+               }()
+
+               // The builtin package needs special treatment: its symbols are lower
+               // case but we want to see them, always.
+               if pkg.build.ImportPath == "builtin" {
+                       unexported = true
                }
-               pkgError, ok := e.(PackageError)
-               if ok {
-                       err = pkgError
+
+               switch {
+               case symbol == "":
+                       pkg.packageDoc() // The package exists, so we got some output.
                        return
+               case method == "":
+                       if pkg.symbolDoc(symbol) {
+                               return
+                       }
+               default:
+                       if pkg.methodDoc(symbol, method) {
+                               return
+                       }
                }
-               panic(e)
-       }()
-
-       // The builtin package needs special treatment: its symbols are lower
-       // case but we want to see them, always.
-       if pkg.build.ImportPath == "builtin" {
-               unexported = true
        }
+}
 
-       switch {
-       case symbol == "":
-               pkg.packageDoc()
-               return
-       case method == "":
-               pkg.symbolDoc(symbol)
-       default:
-               pkg.methodDoc(symbol, method)
+// failMessage creates a nicely formatted error message when there is no result to show.
+func failMessage(paths []string, symbol, method string) error {
+       var b bytes.Buffer
+       if len(paths) > 1 {
+               b.WriteString("s")
+       }
+       b.WriteString(" ")
+       for i, path := range paths {
+               if i > 0 {
+                       b.WriteString(", ")
+               }
+               b.WriteString(path)
        }
-       return nil
+       if method == "" {
+               return fmt.Errorf("no symbol %s in package%s", symbol, &b)
+       }
+       return fmt.Errorf("no method %s.%s in package%s", symbol, method, &b)
 }
 
 // parseArgs analyzes the arguments (if any) and returns the package
 // the path (or "" if it's the current package) and the symbol
 // (possibly with a .method) within that package.
 // parseSymbol is used to analyze the symbol itself.
-func parseArgs(args []string) (*build.Package, string, string) {
+// The boolean final argument reports whether it is possible that
+// there may be more directories worth looking at. It will only
+// be true if the package path is a partial match for some directory
+// and there may be more matches. For example, if the argument
+// is rand.Float64, we must scan both crypto/rand and math/rand
+// to find the symbol, and the first call will return crypto/rand, true.
+func parseArgs(args []string) (pkg *build.Package, path, symbol string, more bool) {
        switch len(args) {
        default:
                usage()
        case 0:
                // Easy: current directory.
-               return importDir(pwd()), "", ""
+               return importDir(pwd()), "", "", false
        case 1:
                // Done below.
        case 2:
                if err != nil {
                        log.Fatalf("%s", err)
                }
-               return pkg, args[0], args[1]
+               return pkg, args[0], args[1], false
        }
        // Usual case: one argument.
        arg := args[0]
        // package paths as their prefix.
        pkg, err := build.Import(arg, "", build.ImportComment)
        if err == nil {
-               return pkg, arg, ""
+               return pkg, arg, "", false
        }
        // Another disambiguator: If the symbol starts with an upper
        // case letter, it can only be a symbol in the current directory.
        if isUpper(arg) {
                pkg, err := build.ImportDir(".", build.ImportComment)
                if err == nil {
-                       return pkg, "", arg
+                       return pkg, "", arg, false
                }
        }
        // If it has a slash, it must be a package path but there is a symbol.
                // Have we identified a package already?
                pkg, err := build.Import(arg[0:period], "", build.ImportComment)
                if err == nil {
-                       return pkg, arg[0:period], symbol
+                       return pkg, arg[0:period], symbol, false
                }
                // See if we have the basename or tail of a package, as in json for encoding/json
                // or ivy/value for robpike.io/ivy/value.
-               path := findPackage(arg[0:period])
-               if path != "" {
-                       return importDir(path), arg[0:period], symbol
+               // Launch findPackage as a goroutine so it can return multiple paths if required.
+               path, ok := findPackage(arg[0:period])
+               if ok {
+                       return importDir(path), arg[0:period], symbol, true
                }
+               dirs.Reset() // Next iteration of for loop must scan all the directories again.
        }
        // If it has a slash, we've failed.
        if slash >= 0 {
                log.Fatalf("no such package %s", arg[0:period])
        }
        // Guess it's a symbol in the current directory.
-       return importDir(pwd()), "", arg
+       return importDir(pwd()), "", arg, false
 }
 
 // importDir is just an error-catching wrapper for build.ImportDir.
        return unicode.IsUpper(ch)
 }
 
-// findPackage returns the full file name path specified by the
-// (perhaps partial) package path pkg.
-func findPackage(pkg string) string {
-       if pkg == "" {
-               return ""
-       }
-       if isUpper(pkg) {
-               return "" // Upper case symbol cannot be a package name.
+// findPackage returns the full file name path that first matches the
+// (perhaps partial) package path pkg. The boolean reports if any match was found.
+func findPackage(pkg string) (string, bool) {
+       if pkg == "" || isUpper(pkg) { // Upper case symbol cannot be a package name.
+               return "", false
        }
-       path := pathFor(build.Default.GOROOT, pkg)
-       if path != "" {
-               return path
-       }
-       for _, root := range splitGopath() {
-               path = pathFor(root, pkg)
-               if path != "" {
-                       return path
+       pkgString := filepath.Clean(string(filepath.Separator) + pkg)
+       for {
+               path, ok := dirs.Next()
+               if !ok {
+                       return "", false
+               }
+               if strings.HasSuffix(path, pkgString) {
+                       return path, true
                }
        }
-       return ""
+       return "", false
 }
 
 // splitGopath splits $GOPATH into a list of roots.
        return filepath.SplitList(build.Default.GOPATH)
 }
 
-// pathsFor recursively walks the tree at root looking for possible directories for the package:
-// those whose package path is pkg or which have a proper suffix pkg.
-func pathFor(root, pkg string) (result string) {
-       root = path.Join(root, "src")
-       slashDot := string(filepath.Separator) + "."
-       // We put a slash on the pkg so can use simple string comparison below
-       // yet avoid inadvertent matches, like /foobar matching bar.
-       pkgString := filepath.Clean(string(filepath.Separator) + pkg)
-
-       // We use panic/defer to short-circuit processing at the first match.
-       // A nil panic reports that the path has been found.
-       defer func() {
-               err := recover()
-               if err != nil {
-                       panic(err)
-               }
-       }()
-
-       visit := func(pathName string, f os.FileInfo, err error) error {
-               if err != nil {
-                       return nil
-               }
-               // One package per directory. Ignore the files themselves.
-               if !f.IsDir() {
-                       return nil
-               }
-               // No .git or other dot nonsense please.
-               if strings.Contains(pathName, slashDot) {
-                       return filepath.SkipDir
-               }
-               // Is the tail of the path correct?
-               if strings.HasSuffix(pathName, pkgString) && hasGoFiles(pathName) {
-                       result = pathName
-                       panic(nil)
-               }
-               return nil
-       }
-
-       filepath.Walk(root, visit)
-       return "" // Call to panic above sets the real value.
-}
-
-// hasGoFiles tests whether the directory contains at least one file with ".go"
-// extension
-func hasGoFiles(path string) bool {
-       dir, err := os.Open(path)
-       if err != nil {
-               // ignore unreadable directories
-               return false
-       }
-       defer dir.Close()
-
-       names, err := dir.Readdirnames(0)
-       if err != nil {
-               // ignore unreadable directories
-               return false
-       }
-
-       for _, name := range names {
-               if strings.HasSuffix(name, ".go") {
-                       return true
-               }
-       }
-
-       return false
-}
-
 // pwd returns the current directory.
 func pwd() string {
        wd, err := os.Getwd()
 
        "io"
        "log"
        "os"
+       "path/filepath"
+       "strings"
        "unicode"
        "unicode/utf8"
 )
        return string(p)
 }
 
+// prettyPath returns a version of the package path that is suitable for an
+// error message. It obeys the import comment if present. Also, since
+// pkg.build.ImportPath is sometimes the unhelpful "" or ".", it looks for a
+// directory name in GOROOT or GOPATH if that happens.
+func (pkg *Package) prettyPath() string {
+       path := pkg.build.ImportComment
+       if path == "" {
+               path = pkg.build.ImportPath
+       }
+       if path != "." && path != "" {
+               return path
+       }
+       // Conver the source directory into a more useful path.
+       path = filepath.Clean(pkg.build.Dir)
+       // Can we find a decent prefix?
+       goroot := filepath.Join(build.Default.GOROOT, "src")
+       if strings.HasPrefix(path, goroot) {
+               return path[len(goroot)+1:]
+       }
+       for _, gopath := range splitGopath() {
+               if strings.HasPrefix(path, gopath) {
+                       return path[len(gopath)+1:]
+               }
+       }
+       return path
+}
+
 // pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the
 // main do function, so it doesn't cause an exit. Allows testing to work
 // without running a subprocess. The log prefix will be added when
 // symbolDoc prints the docs for symbol. There may be multiple matches.
 // If symbol matches a type, output includes its methods factories and associated constants.
 // If there is no top-level symbol, symbolDoc looks for methods that match.
-func (pkg *Package) symbolDoc(symbol string) {
+func (pkg *Package) symbolDoc(symbol string) bool {
        defer pkg.flush()
        found := false
        // Functions.
        if !found {
                // See if there are methods.
                if !pkg.printMethodDoc("", symbol) {
-                       log.Printf("symbol %s not present in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
+                       return false
                }
        }
+       return true
 }
 
 // trimUnexportedElems modifies spec in place to elide unexported fields from
 }
 
 // methodDoc prints the docs for matches of symbol.method.
-func (pkg *Package) methodDoc(symbol, method string) {
+func (pkg *Package) methodDoc(symbol, method string) bool {
        defer pkg.flush()
-       if !pkg.printMethodDoc(symbol, method) {
-               pkg.Fatalf("no method %s.%s in package %s installed in %q", symbol, method, pkg.name, pkg.build.ImportPath)
-       }
+       return pkg.printMethodDoc(symbol, method)
 }
 
 // match reports whether the user's symbol matches the program's.