diff --git a/go/packages/golist.go b/go/packages/golist.go index 88ca6691de..a7bd92966f 100644 --- a/go/packages/golist.go +++ b/go/packages/golist.go @@ -544,6 +544,25 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse module: p.Module, } + if (state.cfg.Mode&TypecheckCgo) != 0 && len(p.CgoFiles) != 0 { + if len(p.CompiledGoFiles) > len(p.GoFiles) { + // We need the cgo definitions, which are in the first + // CompiledGoFile after the non-cgo ones. This is a hack but there + // isn't currently a better way to find it. We also need the pure + // Go files and unprocessed cgo files, all of which are already + // in pkg.GoFiles. + cgoTypes := p.CompiledGoFiles[len(p.GoFiles)] + pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...) + } else { + // golang/go#38990: go list silently fails to do cgo processing + pkg.CompiledGoFiles = nil + pkg.Errors = append(pkg.Errors, Error{ + Msg: "go list failed to return CompiledGoFiles; https://golang.org/issue/38990?", + Kind: ListError, + }) + } + } + // Work around https://golang.org/issue/28749: // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. // Filter out any elements of CompiledGoFiles that are also in OtherFiles. diff --git a/go/packages/packages.go b/go/packages/packages.go index 03fd999c0c..1df46ccccf 100644 --- a/go/packages/packages.go +++ b/go/packages/packages.go @@ -19,6 +19,7 @@ import ( "log" "os" "path/filepath" + "reflect" "strings" "sync" @@ -49,6 +50,10 @@ const ( // NeedCompiledGoFiles adds CompiledGoFiles. NeedCompiledGoFiles + // TypecheckCgo enables full support for type checking cgo. Requires Go 1.15+. + // Has no effect without NeedTypes. + TypecheckCgo + // NeedImports adds Imports. If NeedDeps is not set, the Imports field will contain // "placeholder" Packages with only the ID set. NeedImports @@ -257,7 +262,7 @@ type Package struct { GoFiles []string // CompiledGoFiles lists the absolute file paths of the package's source - // files that were presented to the compiler. + // files that are suitable for type checking. // This may differ from GoFiles if files are processed before compilation. CompiledGoFiles []string @@ -878,6 +883,19 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { Error: appendError, Sizes: ld.sizes, } + if (ld.Mode & TypecheckCgo) != 0 { + // TODO: remove this when we stop supporting 1.14. + rtc := reflect.ValueOf(tc).Elem() + usesCgo := rtc.FieldByName("UsesCgo") + if !usesCgo.IsValid() { + appendError(Error{ + Msg: "TypecheckCgo requires Go 1.15+", + Kind: ListError, + }) + return + } + usesCgo.SetBool(true) + } types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) lpkg.importErrors = nil // no longer needed diff --git a/go/packages/packages115_test.go b/go/packages/packages115_test.go index 004de7cc22..3a3187efa0 100644 --- a/go/packages/packages115_test.go +++ b/go/packages/packages115_test.go @@ -7,6 +7,7 @@ package packages_test import ( + "strings" "testing" "golang.org/x/tools/go/packages" @@ -41,3 +42,51 @@ func testInvalidFilesInXTest(t *testing.T, exporter packagestest.Exporter) { t.Errorf("expected 3 packages, got %d", len(initial)) } } + +func TestTypecheckCgo(t *testing.T) { + packagestest.TestAll(t, testTypecheckCgo) +} + +func testTypecheckCgo(t *testing.T, exporter packagestest.Exporter) { + // The android builders have a complex setup which causes this test to fail. See discussion on + // golang.org/cl/214943 for more details. + if !hasGoBuild() { + t.Skip("this test can't run on platforms without go build. See discussion on golang.org/cl/214943 for more details.") + } + + const cgo = `package cgo + import "C" + + func Example() { + C.CString("hi") + } + ` + exported := packagestest.Export(t, exporter, []packagestest.Module{ + { + Name: "golang.org/fake", + Files: map[string]interface{}{ + "cgo/cgo.go": cgo, + }, + }, + }) + defer exported.Cleanup() + + exported.Config.Mode = packages.NeedFiles | packages.NeedCompiledGoFiles | + packages.NeedSyntax | packages.NeedDeps | packages.NeedTypes | + packages.TypecheckCgo + + initial, err := packages.Load(exported.Config, "golang.org/fake/cgo") + if err != nil { + t.Fatal(err) + } + pkg := initial[0] + if len(pkg.Errors) != 0 { + t.Fatalf("package has errors: %v", pkg.Errors) + } + + expos := pkg.Types.Scope().Lookup("Example").Pos() + fname := pkg.Fset.File(expos).Name() + if !strings.HasSuffix(fname, "cgo.go") { + t.Errorf("position for cgo package was loaded from %v, wanted cgo.go", fname) + } +}