go/packages: handle ad-hoc overlays with sources outside modules

If a user runs a file= query for a source that doesn't exist,
and they are outside of a module, fill in the GoSources for the
empty package that go list returns, so the overlay can be applied.

Also add a hack for the case where go list can't determine the compiler
(gc or gccgo) a user is using because
    go list -f "{{context.Compiler}}" -- unsafe
doesn't work. If go list complains that a user is outside a module,
we'll give ourselves flexibility to guess things because there's
no right answer. So we'll guess that the compiler is gc.

Fixes golang/go#33482

Change-Id: I6a8aa0c617c4d803558389fb4272854245f59c5a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/189322
Run-TryBot: Michael Matloob <matloob@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/go/internal/packagesdriver/sizes.go b/go/internal/packagesdriver/sizes.go
index fdc7da0..ea15d57 100644
--- a/go/internal/packagesdriver/sizes.go
+++ b/go/internal/packagesdriver/sizes.go
@@ -82,15 +82,28 @@
 	args = append(args, buildFlags...)
 	args = append(args, "--", "unsafe")
 	stdout, err := InvokeGo(ctx, env, dir, usesExportData, args...)
+	var goarch, compiler string
 	if err != nil {
-		return nil, err
+		if strings.Contains(err.Error(), "cannot find main module") {
+			// User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc.
+			// TODO(matloob): Is this a problem in practice?
+			envout, enverr := InvokeGo(ctx, env, dir, usesExportData, "env", "GOARCH")
+			if enverr != nil {
+				return nil, err
+			}
+			goarch = strings.TrimSpace(envout.String())
+			compiler = "gc"
+		} else {
+			return nil, err
+		}
+	} else {
+		fields := strings.Fields(stdout.String())
+		if len(fields) < 2 {
+			return nil, fmt.Errorf("could not determine GOARCH and Go compiler")
+		}
+		goarch = fields[0]
+		compiler = fields[1]
 	}
-	fields := strings.Fields(stdout.String())
-	if len(fields) < 2 {
-		return nil, fmt.Errorf("could not determine GOARCH and Go compiler")
-	}
-	goarch := fields[0]
-	compiler := fields[1]
 	return types.SizesFor(compiler, goarch), nil
 }
 
diff --git a/go/packages/golist.go b/go/packages/golist.go
index db3feaa..9f8d4ce 100644
--- a/go/packages/golist.go
+++ b/go/packages/golist.go
@@ -258,6 +258,21 @@
 				// Return the original error if the attempt to fall back failed.
 				return err
 			}
+			// Special case to handle issue #33482:
+			// If this is a file= query for ad-hoc packages where the file only exists on an overlay,
+			// and exists outside of a module, add the file in for the package.
+			if len(dirResponse.Packages) == 1 && len(dirResponse.Packages) == 1 &&
+				dirResponse.Packages[0].ID == "command-line-arguments" && len(dirResponse.Packages[0].GoFiles) == 0 {
+				filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath
+				// TODO(matloob): check if the file is outside of a root dir?
+				for path := range cfg.Overlay {
+					if path == filename {
+						dirResponse.Packages[0].Errors = nil
+						dirResponse.Packages[0].GoFiles = []string{path}
+						dirResponse.Packages[0].CompiledGoFiles = []string{path}
+					}
+				}
+			}
 		}
 		isRoot := make(map[string]bool, len(dirResponse.Roots))
 		for _, root := range dirResponse.Roots {
diff --git a/go/packages/golist_overlay.go b/go/packages/golist_overlay.go
index 298308c..b051327 100644
--- a/go/packages/golist_overlay.go
+++ b/go/packages/golist_overlay.go
@@ -50,7 +50,7 @@
 		}
 	nextPackage:
 		for _, p := range response.dr.Packages {
-			if pkgName != p.Name {
+			if pkgName != p.Name && p.ID != "command-line-arguments" {
 				continue
 			}
 			for _, f := range p.GoFiles {
diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go
index 8385230..9f81b4a 100644
--- a/go/packages/packages_test.go
+++ b/go/packages/packages_test.go
@@ -1011,9 +1011,6 @@
 }
 
 func TestAdHocOverlays(t *testing.T) {
-	// Enable this test when https://golang.org/issue/33482 is resolved.
-	t.Skip()
-
 	// This test doesn't use packagestest because we are testing ad-hoc packages,
 	// which are outside of $GOPATH and outside of a module.
 	tmp, err := ioutil.TempDir("", "a")
@@ -1039,6 +1036,9 @@
 	}
 	// Check value of a.A.
 	a := initial[0]
+	if a.Errors != nil {
+		t.Fatalf("a: got errors %+v, want no error", err)
+	}
 	aA := constant(a, "A")
 	if aA == nil {
 		t.Errorf("a.A: got nil")
@@ -1048,7 +1048,6 @@
 	if want := "1"; got != want {
 		t.Errorf("a.A: got %s, want %s", got, want)
 	}
-
 }
 
 func TestLoadAllSyntaxImportErrors(t *testing.T) {
diff --git a/gopls/go.sum b/gopls/go.sum
index 468c83d..6adfd65 100644
--- a/gopls/go.sum
+++ b/gopls/go.sum
@@ -6,3 +6,4 @@
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/tools v0.0.0-20190723021737-8bb11ff117ca h1:SqwJrz6xPBlCUltcEHz2/p01HRPR+VGD+aYLikk8uas=
 golang.org/x/tools v0.0.0-20190723021737-8bb11ff117ca/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=