mirror of
https://github.com/golang/go
synced 2024-11-18 10:04:43 -07:00
internal/lsp: show compiler optimization decisions
The gc compiler will report its decisions about inlining, escapes, etc. This can be turned on and off with a new optional code lens gc_details. When enabled, the code lens will be displayed above the package statement. The compiler's decisions are shown as information diagnostics. (Other diagnostics have been errors and warnings.) Change-Id: I7d1d5b5b5cf8acd7ff08f683e537ea618e269547 Reviewed-on: https://go-review.googlesource.com/c/tools/+/243119 Run-TryBot: Peter Weinberger <pjw@google.com> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
edd3c8e9e2
commit
95780ea8b3
@ -86,14 +86,24 @@ Example Usage:
|
|||||||
### **codelens** *map[string]bool*
|
### **codelens** *map[string]bool*
|
||||||
|
|
||||||
Overrides the enabled/disabled state of various code lenses. Currently, we
|
Overrides the enabled/disabled state of various code lenses. Currently, we
|
||||||
support two code lenses:
|
support several code lenses:
|
||||||
|
|
||||||
* `generate`: [default: enabled] run `go generate` as specified by a `//go:generate` directive.
|
* `generate`: [default: enabled] run `go generate` as specified by a `//go:generate` directive.
|
||||||
* `upgrade.dependency`: [default: enabled] upgrade a dependency listed in a `go.mod` file.
|
* `upgrade_dependency`: [default: enabled] upgrade a dependency listed in a `go.mod` file.
|
||||||
* `test`: [default: disabled] run `go test -run` for a test func.
|
* `test`: [default: disabled] run `go test -run` for a test func.
|
||||||
|
* `gc_details`: [default: disabled] Show the gc compiler's choices for inline analysis and escaping.
|
||||||
|
|
||||||
By default, both of these code lenses are enabled.
|
Example Usage:
|
||||||
|
```json5
|
||||||
|
"gopls": {
|
||||||
|
...
|
||||||
|
"codelens": {
|
||||||
|
"generate": false, // Don't run `go generate`.
|
||||||
|
"gc_details": true // Show a code lens toggling the display of gc's choices.
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
### **completionDocumentation** *boolean*
|
### **completionDocumentation** *boolean*
|
||||||
|
|
||||||
If false, indicates that the user does not want documentation with completion results.
|
If false, indicates that the user does not want documentation with completion results.
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/event"
|
"golang.org/x/tools/internal/event"
|
||||||
@ -50,8 +51,9 @@ func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if unsaved {
|
if unsaved {
|
||||||
switch command {
|
switch params.Command {
|
||||||
case source.CommandTest, source.CommandGenerate:
|
case source.CommandTest.Name, source.CommandGenerate.Name, source.CommandToggleDetails.Name:
|
||||||
|
// TODO(PJW): for Toggle, not an error if it is being disabled
|
||||||
return nil, s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
|
return nil, s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
|
||||||
Type: protocol.Error,
|
Type: protocol.Error,
|
||||||
Message: fmt.Sprintf("cannot run command %s: unsaved files in the view", params.Command),
|
Message: fmt.Sprintf("cannot run command %s: unsaved files in the view", params.Command),
|
||||||
@ -145,6 +147,29 @@ func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCom
|
|||||||
return nil, err
|
return nil, err
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown command: %s", params.Command)
|
return nil, fmt.Errorf("unknown command: %s", params.Command)
|
||||||
|
case source.CommandToggleDetails:
|
||||||
|
var fileURI span.URI
|
||||||
|
if err := source.UnmarshalArgs(params.Arguments, &fileURI); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pkgDir := span.URI(path.Dir(fileURI.Filename()))
|
||||||
|
s.deliveredMu.Lock()
|
||||||
|
if s.gcOptimizatonDetails[pkgDir] {
|
||||||
|
delete(s.gcOptimizatonDetails, pkgDir)
|
||||||
|
} else {
|
||||||
|
s.gcOptimizatonDetails[pkgDir] = true
|
||||||
|
}
|
||||||
|
event.Log(ctx, fmt.Sprintf("PJW details %s now %v %v", pkgDir, s.gcOptimizatonDetails[pkgDir],
|
||||||
|
s.gcOptimizatonDetails))
|
||||||
|
s.deliveredMu.Unlock()
|
||||||
|
// need to recompute diagnostics.
|
||||||
|
// so find the snapshot
|
||||||
|
sv, err := s.session.ViewOf(fileURI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.diagnoseSnapshot(sv.Snapshot())
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ import (
|
|||||||
"golang.org/x/tools/internal/lsp/mod"
|
"golang.org/x/tools/internal/lsp/mod"
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
"golang.org/x/tools/internal/xcontext"
|
"golang.org/x/tools/internal/xcontext"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
@ -115,16 +117,32 @@ If you believe this is a mistake, please file an issue: https://github.com/golan
|
|||||||
go func(pkg source.Package) {
|
go func(pkg source.Package) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
|
detailsDir := ""
|
||||||
// Only run analyses for packages with open files.
|
// Only run analyses for packages with open files.
|
||||||
withAnalysis := alwaysAnalyze
|
withAnalysis := alwaysAnalyze
|
||||||
for _, pgf := range pkg.CompiledGoFiles() {
|
for _, pgf := range pkg.CompiledGoFiles() {
|
||||||
if snapshot.IsOpen(pgf.URI) {
|
if snapshot.IsOpen(pgf.URI) {
|
||||||
withAnalysis = true
|
withAnalysis = true
|
||||||
break
|
}
|
||||||
|
if detailsDir == "" {
|
||||||
|
dir := filepath.Dir(pgf.URI.Filename())
|
||||||
|
if s.gcOptimizatonDetails[span.URI(dir)] {
|
||||||
|
detailsDir = dir
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pkgReports, warn, err := source.Diagnostics(ctx, snapshot, pkg, withAnalysis)
|
pkgReports, warn, err := source.Diagnostics(ctx, snapshot, pkg, withAnalysis)
|
||||||
|
if detailsDir != "" {
|
||||||
|
var more map[source.FileIdentity][]*source.Diagnostic
|
||||||
|
more, err = source.DoGcDetails(ctx, snapshot, detailsDir)
|
||||||
|
if err != nil {
|
||||||
|
event.Error(ctx, "warning: gcdetails", err, tag.Snapshot.Of(snapshot.ID()))
|
||||||
|
}
|
||||||
|
for k, v := range more {
|
||||||
|
pkgReports[k] = append(pkgReports[k], v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if might want to warn the user about their build configuration.
|
// Check if might want to warn the user about their build configuration.
|
||||||
// Our caller decides whether to send the message.
|
// Our caller decides whether to send the message.
|
||||||
|
@ -23,10 +23,11 @@ const concurrentAnalyses = 1
|
|||||||
// messages on on the supplied stream.
|
// messages on on the supplied stream.
|
||||||
func NewServer(session source.Session, client protocol.Client) *Server {
|
func NewServer(session source.Session, client protocol.Client) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
delivered: make(map[span.URI]sentDiagnostics),
|
delivered: make(map[span.URI]sentDiagnostics),
|
||||||
session: session,
|
gcOptimizatonDetails: make(map[span.URI]bool),
|
||||||
client: client,
|
session: session,
|
||||||
diagnosticsSema: make(chan struct{}, concurrentAnalyses),
|
client: client,
|
||||||
|
diagnosticsSema: make(chan struct{}, concurrentAnalyses),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +74,10 @@ type Server struct {
|
|||||||
deliveredMu sync.Mutex
|
deliveredMu sync.Mutex
|
||||||
delivered map[span.URI]sentDiagnostics
|
delivered map[span.URI]sentDiagnostics
|
||||||
|
|
||||||
|
// gcOptimizationDetails describes which packages we want optimization details
|
||||||
|
// included in the diagnostics. The key is the directory of the package.
|
||||||
|
gcOptimizatonDetails map[span.URI]bool
|
||||||
|
|
||||||
// diagnosticsSema limits the concurrency of diagnostics runs, which can be expensive.
|
// diagnosticsSema limits the concurrency of diagnostics runs, which can be expensive.
|
||||||
diagnosticsSema chan struct{}
|
diagnosticsSema chan struct{}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ var lensFuncs = map[string]lensFunc{
|
|||||||
CommandGenerate.Name: goGenerateCodeLens,
|
CommandGenerate.Name: goGenerateCodeLens,
|
||||||
CommandTest.Name: runTestCodeLens,
|
CommandTest.Name: runTestCodeLens,
|
||||||
CommandRegenerateCgo.Name: regenerateCgoLens,
|
CommandRegenerateCgo.Name: regenerateCgoLens,
|
||||||
|
CommandToggleDetails.Name: toggleDetailsCodeLens,
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeLens computes code lens for Go source code.
|
// CodeLens computes code lens for Go source code.
|
||||||
@ -221,3 +222,26 @@ func regenerateCgoLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
|
||||||
|
_, pgf, err := getParsedFile(ctx, snapshot, fh, WidestPackage)
|
||||||
|
fset := snapshot.View().Session().Cache().FileSet()
|
||||||
|
rng, err := newMappedRange(fset, pgf.Mapper, pgf.File.Package, pgf.File.Package).Range()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
jsonArgs, err := MarshalArgs(fh.URI())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []protocol.CodeLens{
|
||||||
|
{
|
||||||
|
Range: rng,
|
||||||
|
Command: protocol.Command{
|
||||||
|
Title: "Toggle gc annotation details",
|
||||||
|
Command: CommandToggleDetails.Name,
|
||||||
|
Arguments: jsonArgs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -53,6 +53,7 @@ var Commands = []*Command{
|
|||||||
CommandVendor,
|
CommandVendor,
|
||||||
CommandExtractVariable,
|
CommandExtractVariable,
|
||||||
CommandExtractFunction,
|
CommandExtractFunction,
|
||||||
|
CommandToggleDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -86,6 +87,11 @@ var (
|
|||||||
Name: "regenerate_cgo",
|
Name: "regenerate_cgo",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommandToggleDetails controls calculation of gc annotations.
|
||||||
|
CommandToggleDetails = &Command{
|
||||||
|
Name: "gc_details",
|
||||||
|
}
|
||||||
|
|
||||||
// CommandFillStruct is a gopls command to fill a struct with default
|
// CommandFillStruct is a gopls command to fill a struct with default
|
||||||
// values.
|
// values.
|
||||||
CommandFillStruct = &Command{
|
CommandFillStruct = &Command{
|
||||||
|
121
internal/lsp/source/gc_annotations.go
Normal file
121
internal/lsp/source/gc_annotations.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// Copyright 2020 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 source
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DoGcDetails(ctx context.Context, snapshot Snapshot, pkgDir string) (map[FileIdentity][]*Diagnostic, error) {
|
||||||
|
outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid()))
|
||||||
|
if err := os.MkdirAll(outDir, 0700); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
args := []string{fmt.Sprintf("-gcflags=-json=0,%s", outDir), pkgDir}
|
||||||
|
err := snapshot.RunGoCommandDirect(ctx, "build", args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
files, err := findJSONFiles(outDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reports := make(map[FileIdentity][]*Diagnostic)
|
||||||
|
var parseError error
|
||||||
|
for _, fn := range files {
|
||||||
|
fname, v, err := parseDetailsFile(fn)
|
||||||
|
if err != nil {
|
||||||
|
// expect errors for all the files, save 1
|
||||||
|
parseError = err
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(fname, ".go") {
|
||||||
|
continue // <autogenerated>
|
||||||
|
}
|
||||||
|
uri := span.URIFromPath(fname)
|
||||||
|
x := snapshot.FindFile(uri)
|
||||||
|
if x == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k := x.Identity()
|
||||||
|
reports[k] = v
|
||||||
|
}
|
||||||
|
return reports, parseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDetailsFile(fn string) (string, []*Diagnostic, error) {
|
||||||
|
buf, err := ioutil.ReadFile(fn)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err // This is an internal error. Likely ever file will fail.
|
||||||
|
}
|
||||||
|
var fname string
|
||||||
|
var ans []*Diagnostic
|
||||||
|
lines := bytes.Split(buf, []byte{'\n'})
|
||||||
|
for i, l := range lines {
|
||||||
|
if len(l) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
x := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal(l, &x); err != nil {
|
||||||
|
return "", nil, fmt.Errorf("internal error (%v) parsing first line of json file %s",
|
||||||
|
err, fn)
|
||||||
|
}
|
||||||
|
fname = x["file"].(string)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
y := protocol.Diagnostic{}
|
||||||
|
if err := json.Unmarshal(l, &y); err != nil {
|
||||||
|
return "", nil, fmt.Errorf("internal error (%#v) parsing json file for %s", err, fname)
|
||||||
|
}
|
||||||
|
y.Range.Start.Line-- // change from 1-based to 0-based
|
||||||
|
y.Range.Start.Character--
|
||||||
|
y.Range.End.Line--
|
||||||
|
y.Range.End.Character--
|
||||||
|
msg := y.Code.(string)
|
||||||
|
if y.Message != "" {
|
||||||
|
msg = fmt.Sprintf("%s(%s)", msg, y.Message)
|
||||||
|
}
|
||||||
|
x := Diagnostic{
|
||||||
|
Range: y.Range,
|
||||||
|
Message: msg,
|
||||||
|
Source: y.Source,
|
||||||
|
Severity: y.Severity,
|
||||||
|
}
|
||||||
|
for _, ri := range y.RelatedInformation {
|
||||||
|
x.Related = append(x.Related, RelatedInformation{
|
||||||
|
URI: ri.Location.URI.SpanURI(),
|
||||||
|
Range: ri.Location.Range,
|
||||||
|
Message: ri.Message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ans = append(ans, &x)
|
||||||
|
}
|
||||||
|
return fname, ans, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findJSONFiles(dir string) ([]string, error) {
|
||||||
|
ans := []string{}
|
||||||
|
f := func(path string, fi os.FileInfo, err error) error {
|
||||||
|
if fi.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(path, ".json") {
|
||||||
|
ans = append(ans, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := filepath.Walk(dir, f)
|
||||||
|
return ans, err
|
||||||
|
}
|
@ -102,6 +102,7 @@ func DefaultOptions() Options {
|
|||||||
CommandGenerate.Name: true,
|
CommandGenerate.Name: true,
|
||||||
CommandUpgradeDependency.Name: true,
|
CommandUpgradeDependency.Name: true,
|
||||||
CommandRegenerateCgo.Name: true,
|
CommandRegenerateCgo.Name: true,
|
||||||
|
CommandToggleDetails.Name: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DebuggingOptions: DebuggingOptions{
|
DebuggingOptions: DebuggingOptions{
|
||||||
|
Loading…
Reference in New Issue
Block a user