Skip to content

Commit 66ef73e

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/golang: improve "toggle compiler opt details"
This CL improves the usability of the "Toggle compiler optimization details" code action by: - making it work for _test.go files too (by running "go test -c" instead of "go build"); - changing the flag to be per directory, not per metadata.Package, as this led to confusing behaviour w.r.t. in-package test files. - presenting specific "Show"/"Hide" command titles depending on the current state, instead of the vague "Toggle". It includes a test for all the new functionality. Also, document the "transitively error free" requirement. Fixes golang/go#71106 Fixes golang/go#42812 (amusingly) Change-Id: I842d7e3f53d1c4f0e94b8741ca792c3d999d6ff3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/640395 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Alan Donovan <[email protected]>
1 parent 79cde82 commit 66ef73e

File tree

10 files changed

+178
-73
lines changed

10 files changed

+178
-73
lines changed

gopls/doc/features/diagnostics.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,13 @@ There is an optional third source of diagnostics:
6565

6666
This source is disabled by default but can be enabled on a
6767
package-by-package basis by invoking the
68-
`source.toggleCompilerOptDetails` ("Toggle compiler optimization
68+
`source.toggleCompilerOptDetails` ("{Show,Hide} compiler optimization
6969
details") code action.
7070

71+
Remember that the compiler's optimizer runs only on packages that
72+
are transitively free from errors, so optimization diagnostics
73+
will not be shown on packages that do not build.
74+
7175

7276
## Recomputation of diagnostics
7377

gopls/doc/release/v0.18.0.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212

1313
# New features
1414

15-
## "Toggle compiler optimization details" code action
15+
## "{Show,Hide} compiler optimization details" code action
1616

1717
This code action, accessible through the "Source Action" menu in VS
18-
Code, toggles a per-package flag that causes Go compiler optimization
18+
Code, toggles a per-directory flag that causes Go compiler optimization
1919
details to be reported as diagnostics. For example, it indicates which
2020
variables escape to the heap, and which array accesses require bounds
2121
checks.

gopls/internal/cache/snapshot.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,9 @@ type Snapshot struct {
183183
// vulns maps each go.mod file's URI to its known vulnerabilities.
184184
vulns *persistent.Map[protocol.DocumentURI, *vulncheck.Result]
185185

186-
// compilerOptDetails describes the packages for which we want
187-
// compiler optimization details to be included in the diagnostics.
188-
compilerOptDetails map[metadata.PackageID]unit
186+
// compilerOptDetails is the set of directories whose packages
187+
// and tests need compiler optimization details in the diagnostics.
188+
compilerOptDetails map[protocol.DocumentURI]unit
189189

190190
// Concurrent type checking:
191191
// typeCheckMu guards the ongoing type checking batch, and reference count of
@@ -1523,15 +1523,15 @@ func (s *Snapshot) clone(ctx, bgCtx context.Context, changed StateChange, done f
15231523
// Compute the new set of packages for which we want compiler
15241524
// optimization details, after applying changed.CompilerOptDetails.
15251525
if len(s.compilerOptDetails) > 0 || len(changed.CompilerOptDetails) > 0 {
1526-
newCompilerOptDetails := make(map[metadata.PackageID]unit)
1527-
for id := range s.compilerOptDetails {
1528-
if _, ok := changed.CompilerOptDetails[id]; !ok {
1529-
newCompilerOptDetails[id] = unit{} // no change
1526+
newCompilerOptDetails := make(map[protocol.DocumentURI]unit)
1527+
for dir := range s.compilerOptDetails {
1528+
if _, ok := changed.CompilerOptDetails[dir]; !ok {
1529+
newCompilerOptDetails[dir] = unit{} // no change
15301530
}
15311531
}
1532-
for id, want := range changed.CompilerOptDetails {
1532+
for dir, want := range changed.CompilerOptDetails {
15331533
if want {
1534-
newCompilerOptDetails[id] = unit{}
1534+
newCompilerOptDetails[dir] = unit{}
15351535
}
15361536
}
15371537
if len(newCompilerOptDetails) > 0 {
@@ -2160,9 +2160,9 @@ func (s *Snapshot) setBuiltin(path string) {
21602160
}
21612161

21622162
// WantCompilerOptDetails reports whether to compute compiler
2163-
// optimization details for the specified package.
2164-
func (s *Snapshot) WantCompilerOptDetails(id metadata.PackageID) bool {
2165-
_, ok := s.compilerOptDetails[id]
2163+
// optimization details for packages and tests in the given directory.
2164+
func (s *Snapshot) WantCompilerOptDetails(dir protocol.DocumentURI) bool {
2165+
_, ok := s.compilerOptDetails[dir]
21662166
return ok
21672167
}
21682168

gopls/internal/cache/view.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
"sync"
2828
"time"
2929

30-
"golang.org/x/tools/gopls/internal/cache/metadata"
3130
"golang.org/x/tools/gopls/internal/cache/typerefs"
3231
"golang.org/x/tools/gopls/internal/file"
3332
"golang.org/x/tools/gopls/internal/protocol"
@@ -745,7 +744,7 @@ type StateChange struct {
745744
Files map[protocol.DocumentURI]file.Handle
746745
ModuleUpgrades map[protocol.DocumentURI]map[string]string
747746
Vulns map[protocol.DocumentURI]*vulncheck.Result
748-
CompilerOptDetails map[metadata.PackageID]bool // package -> whether or not we want details
747+
CompilerOptDetails map[protocol.DocumentURI]bool // package directory -> whether or not we want details
749748
}
750749

751750
// InvalidateView processes the provided state change, invalidating any derived

gopls/internal/cmd/integration_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -950,12 +950,12 @@ func TestCodeAction(t *testing.T) {
950950
module example.com
951951
go 1.18
952952
953-
-- a.go --
953+
-- a/a.go --
954954
package a
955955
type T int
956956
func f() (int, string) { return }
957957
958-
-- b.go --
958+
-- a/b.go --
959959
package a
960960
import "io"
961961
var _ io.Reader = C{}
@@ -970,14 +970,14 @@ type C struct{}
970970
}
971971
// list code actions in file
972972
{
973-
res := gopls(t, tree, "codeaction", "a.go")
973+
res := gopls(t, tree, "codeaction", "a/a.go")
974974
res.checkExit(true)
975975
res.checkStdout(`edit "Fill in return values" \[quickfix\]`)
976976
res.checkStdout(`command "Browse documentation for package a" \[source.doc\]`)
977977
}
978978
// list code actions in file, filtering by title
979979
{
980-
res := gopls(t, tree, "codeaction", "-title=Browse.*doc", "a.go")
980+
res := gopls(t, tree, "codeaction", "-title=Browse.*doc", "a/a.go")
981981
res.checkExit(true)
982982
got := res.stdout
983983
want := `command "Browse gopls feature documentation" [gopls.doc.features]` +
@@ -990,26 +990,26 @@ type C struct{}
990990
}
991991
// list code actions in file, filtering (hierarchically) by kind
992992
{
993-
res := gopls(t, tree, "codeaction", "-kind=source", "a.go")
993+
res := gopls(t, tree, "codeaction", "-kind=source", "a/a.go")
994994
res.checkExit(true)
995995
got := res.stdout
996996
want := `command "Browse documentation for package a" [source.doc]` +
997997
"\n" +
998-
`command "Toggle compiler optimization details" [source.toggleCompilerOptDetails]` +
998+
`command "Show compiler optimization details for \"a\"" [source.toggleCompilerOptDetails]` +
999999
"\n"
10001000
if got != want {
10011001
t.Errorf("codeaction: got <<%s>>, want <<%s>>\nstderr:\n%s", got, want, res.stderr)
10021002
}
10031003
}
10041004
// list code actions at position (of io.Reader)
10051005
{
1006-
res := gopls(t, tree, "codeaction", "b.go:#31")
1006+
res := gopls(t, tree, "codeaction", "a/b.go:#31")
10071007
res.checkExit(true)
10081008
res.checkStdout(`command "Browse documentation for type io.Reader" \[source.doc]`)
10091009
}
10101010
// list quick fixes at position (of type T)
10111011
{
1012-
res := gopls(t, tree, "codeaction", "-kind=quickfix", "a.go:#15")
1012+
res := gopls(t, tree, "codeaction", "-kind=quickfix", "a/a.go:#15")
10131013
res.checkExit(true)
10141014
got := res.stdout
10151015
want := `edit "Fill in return values" [quickfix]` + "\n"
@@ -1019,7 +1019,7 @@ type C struct{}
10191019
}
10201020
// success, with explicit CodeAction kind and diagnostics span.
10211021
{
1022-
res := gopls(t, tree, "codeaction", "-kind=quickfix", "-exec", "b.go:#40")
1022+
res := gopls(t, tree, "codeaction", "-kind=quickfix", "-exec", "a/b.go:#40")
10231023
res.checkExit(true)
10241024
got := res.stdout
10251025
want := `

gopls/internal/golang/codeaction.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"go/ast"
1212
"go/token"
1313
"go/types"
14+
"path/filepath"
1415
"reflect"
1516
"slices"
1617
"sort"
@@ -105,6 +106,8 @@ func CodeActions(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle,
105106
req.pkg = nil
106107
}
107108
if err := p.fn(ctx, req); err != nil {
109+
// TODO(adonovan): most errors in code action providers should
110+
// not block other providers; see https://go.dev/issue/71275.
108111
return nil, err
109112
}
110113
}
@@ -879,10 +882,22 @@ func goAssembly(ctx context.Context, req *codeActionsRequest) error {
879882
return nil
880883
}
881884

882-
// toggleCompilerOptDetails produces "Toggle compiler optimization details" code action.
883-
// See [server.commandHandler.ToggleCompilerOptDetails] for command implementation.
885+
// toggleCompilerOptDetails produces "{Show,Hide} compiler optimization details" code action.
886+
// See [server.commandHandler.GCDetails] for command implementation.
884887
func toggleCompilerOptDetails(ctx context.Context, req *codeActionsRequest) error {
885-
cmd := command.NewGCDetailsCommand("Toggle compiler optimization details", req.fh.URI())
886-
req.addCommandAction(cmd, false)
888+
// TODO(adonovan): errors from code action providers should probably be
889+
// logged, even if they aren't visible to the client; see https://go.dev/issue/71275.
890+
if meta, err := NarrowestMetadataForFile(ctx, req.snapshot, req.fh.URI()); err == nil {
891+
if len(meta.CompiledGoFiles) == 0 {
892+
return fmt.Errorf("package %q does not compile file %q", meta.ID, req.fh.URI())
893+
}
894+
dir := meta.CompiledGoFiles[0].Dir()
895+
896+
title := fmt.Sprintf("%s compiler optimization details for %q",
897+
cond(req.snapshot.WantCompilerOptDetails(dir), "Hide", "Show"),
898+
filepath.Base(dir.Path()))
899+
cmd := command.NewGCDetailsCommand(title, req.fh.URI())
900+
req.addCommandAction(cmd, false)
901+
}
887902
return nil
888903
}

gopls/internal/golang/compileropt.go

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,19 @@ import (
1111
"fmt"
1212
"os"
1313
"path/filepath"
14+
"runtime"
1415
"strings"
1516

1617
"golang.org/x/tools/gopls/internal/cache"
17-
"golang.org/x/tools/gopls/internal/cache/metadata"
1818
"golang.org/x/tools/gopls/internal/protocol"
1919
"golang.org/x/tools/internal/event"
2020
)
2121

2222
// CompilerOptDetails invokes the Go compiler with the "-json=0,dir"
23-
// flag on the specified package, parses its log of optimization
24-
// decisions, and returns them as a set of diagnostics.
25-
func CompilerOptDetails(ctx context.Context, snapshot *cache.Snapshot, mp *metadata.Package) (map[protocol.DocumentURI][]*cache.Diagnostic, error) {
26-
if len(mp.CompiledGoFiles) == 0 {
27-
return nil, nil
28-
}
29-
pkgDir := mp.CompiledGoFiles[0].DirPath()
23+
// flag on the packages and tests in the specified directory, parses
24+
// its log of optimization decisions, and returns them as a set of
25+
// diagnostics.
26+
func CompilerOptDetails(ctx context.Context, snapshot *cache.Snapshot, pkgDir protocol.DocumentURI) (map[protocol.DocumentURI][]*cache.Diagnostic, error) {
3027
outDir, err := os.MkdirTemp("", fmt.Sprintf("gopls-%d.details", os.Getpid()))
3128
if err != nil {
3229
return nil, err
@@ -37,22 +34,20 @@ func CompilerOptDetails(ctx context.Context, snapshot *cache.Snapshot, mp *metad
3734
}
3835
}()
3936

40-
tmpFile, err := os.CreateTemp(os.TempDir(), "gopls-x")
41-
if err != nil {
42-
return nil, err
43-
}
44-
tmpFile.Close() // ignore error
45-
defer os.Remove(tmpFile.Name())
46-
4737
outDirURI := protocol.URIFromPath(outDir)
4838
// details doesn't handle Windows URIs in the form of "file:///C:/...",
4939
// so rewrite them to "file://C:/...". See golang/go#41614.
5040
if !strings.HasPrefix(outDir, "/") {
5141
outDirURI = protocol.DocumentURI(strings.Replace(string(outDirURI), "file:///", "file://", 1))
5242
}
53-
inv, cleanupInvocation, err := snapshot.GoCommandInvocation(cache.NoNetwork, pkgDir, "build", []string{
43+
44+
// We use "go test -c" not "go build" as it covers all three packages
45+
// (p, "p [p.test]", "p_test [p.test]") in the directory, if they exist.
46+
inv, cleanupInvocation, err := snapshot.GoCommandInvocation(cache.NoNetwork, pkgDir.Path(), "test", []string{
47+
"-c",
48+
"-vet=off", // weirdly -c doesn't disable vet
5449
fmt.Sprintf("-gcflags=-json=0,%s", outDirURI), // JSON schema version 0
55-
fmt.Sprintf("-o=%s", tmpFile.Name()),
50+
fmt.Sprintf("-o=%s", cond(runtime.GOOS == "windows", "NUL", "/dev/null")),
5651
".",
5752
})
5853
if err != nil {
@@ -79,7 +74,8 @@ func CompilerOptDetails(ctx context.Context, snapshot *cache.Snapshot, mp *metad
7974
if fh == nil {
8075
continue
8176
}
82-
if pkgDir != fh.URI().DirPath() {
77+
if pkgDir != fh.URI().Dir() {
78+
// Filter compiler diagnostics to the requested directory.
8379
// https://github.com/golang/go/issues/42198
8480
// sometimes the detail diagnostics generated for files
8581
// outside the package can never be taken back.

gopls/internal/server/command.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,19 +1017,22 @@ func (s *server) getUpgrades(ctx context.Context, snapshot *cache.Snapshot, uri
10171017

10181018
func (c *commandHandler) GCDetails(ctx context.Context, uri protocol.DocumentURI) error {
10191019
return c.run(ctx, commandConfig{
1020-
progress: "Toggling display of compiler optimization details",
1021-
forURI: uri,
1020+
forURI: uri,
10221021
}, func(ctx context.Context, deps commandDeps) error {
10231022
return c.modifyState(ctx, FromToggleCompilerOptDetails, func() (*cache.Snapshot, func(), error) {
1023+
// Don't blindly use "dir := deps.fh.URI().Dir()"; validate.
10241024
meta, err := golang.NarrowestMetadataForFile(ctx, deps.snapshot, deps.fh.URI())
10251025
if err != nil {
10261026
return nil, nil, err
10271027
}
1028-
want := !deps.snapshot.WantCompilerOptDetails(meta.ID) // toggle per-package flag
1028+
if len(meta.CompiledGoFiles) == 0 {
1029+
return nil, nil, fmt.Errorf("package %q does not compile file %q", meta.ID, deps.fh.URI())
1030+
}
1031+
dir := meta.CompiledGoFiles[0].Dir()
1032+
1033+
want := !deps.snapshot.WantCompilerOptDetails(dir) // toggle per-directory flag
10291034
return c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{
1030-
CompilerOptDetails: map[metadata.PackageID]bool{
1031-
meta.ID: want,
1032-
},
1035+
CompilerOptDetails: map[protocol.DocumentURI]bool{dir: want},
10331036
})
10341037
})
10351038
})

gopls/internal/server/diagnostics.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -561,25 +561,26 @@ func (s *server) compilerOptDetailsDiagnostics(ctx context.Context, snapshot *ca
561561
// TODO(rfindley): This should memoize its results if the package has not changed.
562562
// Consider that these points, in combination with the note below about
563563
// races, suggest that compiler optimization details should be tracked on the Snapshot.
564-
var detailPkgs map[metadata.PackageID]*metadata.Package
565-
for _, mp := range toDiagnose {
566-
if snapshot.WantCompilerOptDetails(mp.ID) {
567-
if detailPkgs == nil {
568-
detailPkgs = make(map[metadata.PackageID]*metadata.Package)
569-
}
570-
detailPkgs[mp.ID] = mp
571-
}
572-
}
573-
574564
diagnostics := make(diagMap)
575-
for _, mp := range detailPkgs {
576-
perFileDiags, err := golang.CompilerOptDetails(ctx, snapshot, mp)
577-
if err != nil {
578-
event.Error(ctx, "warning: compiler optimization details", err, append(snapshot.Labels(), label.Package.Of(string(mp.ID)))...)
565+
seenDirs := make(map[protocol.DocumentURI]bool)
566+
for _, mp := range toDiagnose {
567+
if len(mp.CompiledGoFiles) == 0 {
579568
continue
580569
}
581-
for uri, diags := range perFileDiags {
582-
diagnostics[uri] = append(diagnostics[uri], diags...)
570+
dir := mp.CompiledGoFiles[0].Dir()
571+
if snapshot.WantCompilerOptDetails(dir) {
572+
if !seenDirs[dir] {
573+
seenDirs[dir] = true
574+
575+
perFileDiags, err := golang.CompilerOptDetails(ctx, snapshot, dir)
576+
if err != nil {
577+
event.Error(ctx, "warning: compiler optimization details", err, append(snapshot.Labels(), label.URI.Of(dir))...)
578+
continue
579+
}
580+
for uri, diags := range perFileDiags {
581+
diagnostics[uri] = append(diagnostics[uri], diags...)
582+
}
583+
}
583584
}
584585
}
585586
return diagnostics, nil

0 commit comments

Comments
 (0)