2019-05-10 14:35:43 -06:00
|
|
|
// Copyright 2019 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.
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
package source
|
|
|
|
|
|
|
|
import (
|
2019-08-02 17:45:56 -06:00
|
|
|
"context"
|
2020-07-14 18:40:38 -06:00
|
|
|
"encoding/json"
|
2019-12-09 09:36:55 -07:00
|
|
|
"fmt"
|
2019-04-24 17:26:34 -06:00
|
|
|
"go/ast"
|
2019-11-22 12:49:12 -07:00
|
|
|
"go/printer"
|
2019-04-24 17:26:34 -06:00
|
|
|
"go/token"
|
|
|
|
"go/types"
|
2019-06-27 12:59:09 -06:00
|
|
|
"path/filepath"
|
2019-08-02 17:45:56 -06:00
|
|
|
"regexp"
|
2019-11-20 23:24:43 -07:00
|
|
|
"sort"
|
2019-04-29 19:08:16 -06:00
|
|
|
"strings"
|
2019-08-02 17:45:56 -06:00
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
2019-08-02 17:45:56 -06:00
|
|
|
"golang.org/x/tools/internal/span"
|
2019-08-26 22:26:45 -06:00
|
|
|
errors "golang.org/x/xerrors"
|
2019-04-24 17:26:34 -06:00
|
|
|
)
|
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
type mappedRange struct {
|
|
|
|
spanRange span.Range
|
|
|
|
m *protocol.ColumnMapper
|
|
|
|
|
|
|
|
// protocolRange is the result of converting the spanRange using the mapper.
|
|
|
|
// It is computed on-demand.
|
|
|
|
protocolRange *protocol.Range
|
|
|
|
}
|
|
|
|
|
2019-11-22 13:20:08 -07:00
|
|
|
func newMappedRange(fset *token.FileSet, m *protocol.ColumnMapper, start, end token.Pos) mappedRange {
|
|
|
|
return mappedRange{
|
|
|
|
spanRange: span.Range{
|
|
|
|
FileSet: fset,
|
|
|
|
Start: start,
|
|
|
|
End: end,
|
|
|
|
Converter: m.Converter,
|
|
|
|
},
|
|
|
|
m: m,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
func (s mappedRange) Range() (protocol.Range, error) {
|
|
|
|
if s.protocolRange == nil {
|
|
|
|
spn, err := s.spanRange.Span()
|
|
|
|
if err != nil {
|
|
|
|
return protocol.Range{}, err
|
|
|
|
}
|
|
|
|
prng, err := s.m.Range(spn)
|
|
|
|
if err != nil {
|
|
|
|
return protocol.Range{}, err
|
|
|
|
}
|
|
|
|
s.protocolRange = &prng
|
|
|
|
}
|
|
|
|
return *s.protocolRange, nil
|
|
|
|
}
|
|
|
|
|
2019-08-26 22:26:45 -06:00
|
|
|
func (s mappedRange) Span() (span.Span, error) {
|
|
|
|
return s.spanRange.Span()
|
|
|
|
}
|
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
func (s mappedRange) URI() span.URI {
|
|
|
|
return s.m.URI
|
|
|
|
}
|
|
|
|
|
2019-12-04 11:45:53 -07:00
|
|
|
// getParsedFile is a convenience function that extracts the Package and ParseGoHandle for a File in a Snapshot.
|
2020-01-14 16:29:21 -07:00
|
|
|
// selectPackage is typically Narrowest/WidestPackageHandle below.
|
2019-12-17 16:57:54 -07:00
|
|
|
func getParsedFile(ctx context.Context, snapshot Snapshot, fh FileHandle, selectPackage PackagePolicy) (Package, ParseGoHandle, error) {
|
2019-12-04 11:45:53 -07:00
|
|
|
phs, err := snapshot.PackageHandles(ctx, fh)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
ph, err := selectPackage(phs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2020-07-16 15:37:12 -06:00
|
|
|
pkg, err := ph.Check(ctx, snapshot)
|
2019-12-04 11:45:53 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
pgh, err := pkg.File(fh.URI())
|
2019-12-04 11:45:53 -07:00
|
|
|
return pkg, pgh, err
|
|
|
|
}
|
|
|
|
|
2019-12-07 23:07:30 -07:00
|
|
|
type PackagePolicy func([]PackageHandle) (PackageHandle, error)
|
|
|
|
|
2020-01-14 16:29:21 -07:00
|
|
|
// NarrowestPackageHandle picks the "narrowest" package for a given file.
|
2019-09-09 18:22:42 -06:00
|
|
|
//
|
|
|
|
// By "narrowest" package, we mean the package with the fewest number of files
|
|
|
|
// that includes the given file. This solves the problem of test variants,
|
|
|
|
// as the test will have more files than the non-test package.
|
2020-01-14 16:29:21 -07:00
|
|
|
func NarrowestPackageHandle(handles []PackageHandle) (PackageHandle, error) {
|
2019-09-09 17:26:26 -06:00
|
|
|
if len(handles) < 1 {
|
2020-01-14 16:29:21 -07:00
|
|
|
return nil, errors.Errorf("no PackageHandles")
|
2019-09-09 17:26:26 -06:00
|
|
|
}
|
|
|
|
result := handles[0]
|
|
|
|
for _, handle := range handles[1:] {
|
2019-11-20 14:15:00 -07:00
|
|
|
if result == nil || len(handle.CompiledGoFiles()) < len(result.CompiledGoFiles()) {
|
2019-09-09 17:26:26 -06:00
|
|
|
result = handle
|
2019-09-09 18:22:42 -06:00
|
|
|
}
|
|
|
|
}
|
2019-10-10 13:22:30 -06:00
|
|
|
if result == nil {
|
2020-01-14 16:29:21 -07:00
|
|
|
return nil, errors.Errorf("nil PackageHandles have been returned")
|
2019-10-10 13:22:30 -06:00
|
|
|
}
|
|
|
|
return result, nil
|
2019-09-09 17:26:26 -06:00
|
|
|
}
|
|
|
|
|
2020-01-14 16:29:21 -07:00
|
|
|
// WidestPackageHandle returns the PackageHandle containing the most files.
|
2019-09-09 17:26:26 -06:00
|
|
|
//
|
|
|
|
// This is useful for something like diagnostics, where we'd prefer to offer diagnostics
|
|
|
|
// for as many files as possible.
|
2020-01-14 16:29:21 -07:00
|
|
|
func WidestPackageHandle(handles []PackageHandle) (PackageHandle, error) {
|
2019-09-09 17:26:26 -06:00
|
|
|
if len(handles) < 1 {
|
2020-01-14 16:29:21 -07:00
|
|
|
return nil, errors.Errorf("no PackageHandles")
|
2019-09-09 17:26:26 -06:00
|
|
|
}
|
|
|
|
result := handles[0]
|
|
|
|
for _, handle := range handles[1:] {
|
2019-11-20 14:15:00 -07:00
|
|
|
if result == nil || len(handle.CompiledGoFiles()) > len(result.CompiledGoFiles()) {
|
2019-09-09 17:26:26 -06:00
|
|
|
result = handle
|
|
|
|
}
|
2019-09-09 18:22:42 -06:00
|
|
|
}
|
2019-10-10 13:22:30 -06:00
|
|
|
if result == nil {
|
2020-01-14 16:29:21 -07:00
|
|
|
return nil, errors.Errorf("nil PackageHandles have been returned")
|
2019-10-10 13:22:30 -06:00
|
|
|
}
|
|
|
|
return result, nil
|
2019-09-09 18:22:42 -06:00
|
|
|
}
|
|
|
|
|
2019-12-07 23:07:30 -07:00
|
|
|
// SpecificPackageHandle creates a PackagePolicy to select a
|
|
|
|
// particular PackageHandle when you alread know the one you want.
|
|
|
|
func SpecificPackageHandle(desiredID string) PackagePolicy {
|
|
|
|
return func(handles []PackageHandle) (PackageHandle, error) {
|
|
|
|
for _, h := range handles {
|
|
|
|
if h.ID() == desiredID {
|
|
|
|
return h, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("no package handle with expected id %q", desiredID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-11 21:59:57 -07:00
|
|
|
func IsGenerated(ctx context.Context, snapshot Snapshot, uri span.URI) bool {
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
fh, err := snapshot.GetFile(ctx, uri)
|
2019-08-02 17:45:56 -06:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
ph := snapshot.View().Session().Cache().ParseGoHandle(ctx, fh, ParseHeader)
|
2020-07-16 15:37:12 -06:00
|
|
|
parsed, _, _, _, err := ph.Parse(ctx, snapshot.View())
|
2019-09-17 09:19:11 -06:00
|
|
|
if err != nil {
|
2019-08-02 17:45:56 -06:00
|
|
|
return false
|
|
|
|
}
|
2020-01-11 21:59:57 -07:00
|
|
|
tok := snapshot.View().Session().Cache().FileSet().File(parsed.Pos())
|
2019-08-02 17:45:56 -06:00
|
|
|
if tok == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, commentGroup := range parsed.Comments {
|
|
|
|
for _, comment := range commentGroup.List {
|
|
|
|
if matched := generatedRx.MatchString(comment.Text); matched {
|
|
|
|
// Check if comment is at the beginning of the line in source.
|
|
|
|
if pos := tok.Position(comment.Slash); pos.Column == 1 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-01-15 17:55:03 -07:00
|
|
|
func nodeToProtocolRange(view View, pkg Package, n ast.Node) (protocol.Range, error) {
|
|
|
|
mrng, err := posToMappedRange(view, pkg, n.Pos(), n.End())
|
2019-09-05 16:54:05 -06:00
|
|
|
if err != nil {
|
|
|
|
return protocol.Range{}, err
|
|
|
|
}
|
|
|
|
return mrng.Range()
|
|
|
|
}
|
|
|
|
|
2019-11-22 12:49:12 -07:00
|
|
|
func objToMappedRange(v View, pkg Package, obj types.Object) (mappedRange, error) {
|
2019-09-05 16:54:05 -06:00
|
|
|
if pkgName, ok := obj.(*types.PkgName); ok {
|
|
|
|
// An imported Go package has a package-local, unqualified name.
|
|
|
|
// When the name matches the imported package name, there is no
|
|
|
|
// identifier in the import spec with the local package name.
|
|
|
|
//
|
|
|
|
// For example:
|
|
|
|
// import "go/ast" // name "ast" matches package name
|
|
|
|
// import a "go/ast" // name "a" does not match package name
|
|
|
|
//
|
|
|
|
// When the identifier does not appear in the source, have the range
|
2019-11-19 12:51:46 -07:00
|
|
|
// of the object be the import path, including quotes.
|
2019-09-05 16:54:05 -06:00
|
|
|
if pkgName.Imported().Name() == pkgName.Name() {
|
2019-11-22 12:49:12 -07:00
|
|
|
return posToMappedRange(v, pkg, obj.Pos(), obj.Pos()+token.Pos(len(pkgName.Imported().Path())+2))
|
2019-09-05 16:54:05 -06:00
|
|
|
}
|
|
|
|
}
|
2019-11-22 12:49:12 -07:00
|
|
|
return nameToMappedRange(v, pkg, obj.Pos(), obj.Name())
|
2019-09-05 16:54:05 -06:00
|
|
|
}
|
|
|
|
|
2019-11-22 12:49:12 -07:00
|
|
|
func nameToMappedRange(v View, pkg Package, pos token.Pos, name string) (mappedRange, error) {
|
|
|
|
return posToMappedRange(v, pkg, pos, pos+token.Pos(len(name)))
|
2019-09-05 16:54:05 -06:00
|
|
|
}
|
|
|
|
|
2019-11-22 12:49:12 -07:00
|
|
|
func posToMappedRange(v View, pkg Package, pos, end token.Pos) (mappedRange, error) {
|
2019-11-21 12:54:31 -07:00
|
|
|
logicalFilename := v.Session().Cache().FileSet().File(pos).Position(pos).Filename
|
2020-02-12 14:36:46 -07:00
|
|
|
m, err := findMapperInPackage(v, pkg, span.URIFromPath(logicalFilename))
|
2019-09-16 16:17:51 -06:00
|
|
|
if err != nil {
|
|
|
|
return mappedRange{}, err
|
|
|
|
}
|
2019-09-05 16:54:05 -06:00
|
|
|
if !pos.IsValid() {
|
|
|
|
return mappedRange{}, errors.Errorf("invalid position for %v", pos)
|
|
|
|
}
|
|
|
|
if !end.IsValid() {
|
|
|
|
return mappedRange{}, errors.Errorf("invalid position for %v", end)
|
|
|
|
}
|
2020-01-15 17:55:03 -07:00
|
|
|
return newMappedRange(v.Session().Cache().FileSet(), m, pos, end), nil
|
2019-09-05 16:54:05 -06:00
|
|
|
}
|
|
|
|
|
2019-08-02 17:45:56 -06:00
|
|
|
// Matches cgo generated comment as well as the proposed standard:
|
|
|
|
// https://golang.org/s/generatedcode
|
|
|
|
var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
|
|
|
|
|
2019-06-27 12:59:09 -06:00
|
|
|
func DetectLanguage(langID, filename string) FileKind {
|
|
|
|
switch langID {
|
|
|
|
case "go":
|
|
|
|
return Go
|
|
|
|
case "go.mod":
|
|
|
|
return Mod
|
|
|
|
case "go.sum":
|
|
|
|
return Sum
|
|
|
|
}
|
|
|
|
// Fallback to detecting the language based on the file extension.
|
|
|
|
switch filepath.Ext(filename) {
|
|
|
|
case ".mod":
|
|
|
|
return Mod
|
|
|
|
case ".sum":
|
|
|
|
return Sum
|
|
|
|
default: // fallback to Go
|
|
|
|
return Go
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (k FileKind) String() string {
|
|
|
|
switch k {
|
|
|
|
case Mod:
|
|
|
|
return "go.mod"
|
|
|
|
case Sum:
|
|
|
|
return "go.sum"
|
|
|
|
default:
|
|
|
|
return "go"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-21 17:26:14 -07:00
|
|
|
// Returns the index and the node whose position is contained inside the node list.
|
|
|
|
func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) {
|
|
|
|
if nodes == nil {
|
|
|
|
return nil, -1
|
|
|
|
}
|
|
|
|
for i, node := range nodes {
|
|
|
|
if node.Pos() <= pos && pos <= node.End() {
|
|
|
|
return node, i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, -1
|
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// indexExprAtPos returns the index of the expression containing pos.
|
2020-01-25 16:22:03 -07:00
|
|
|
func exprAtPos(pos token.Pos, args []ast.Expr) int {
|
2019-04-24 17:26:34 -06:00
|
|
|
for i, expr := range args {
|
|
|
|
if expr.Pos() <= pos && pos <= expr.End() {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len(args)
|
|
|
|
}
|
|
|
|
|
2020-03-13 13:10:42 -06:00
|
|
|
// eachField invokes fn for each field that can be selected from a
|
|
|
|
// value of type T.
|
|
|
|
func eachField(T types.Type, fn func(*types.Var)) {
|
2019-04-24 17:26:34 -06:00
|
|
|
// TODO(adonovan): this algorithm doesn't exclude ambiguous
|
|
|
|
// selections that match more than one field/method.
|
|
|
|
// types.NewSelectionSet should do that for us.
|
|
|
|
|
2020-03-13 13:10:42 -06:00
|
|
|
// for termination on recursive types
|
|
|
|
var seen map[*types.Struct]bool
|
2019-06-27 11:50:01 -06:00
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
var visit func(T types.Type)
|
|
|
|
visit = func(T types.Type) {
|
2019-06-27 11:50:01 -06:00
|
|
|
if T, ok := deref(T).Underlying().(*types.Struct); ok {
|
2020-03-13 13:10:42 -06:00
|
|
|
if seen[T] {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-06-27 11:50:01 -06:00
|
|
|
for i := 0; i < T.NumFields(); i++ {
|
|
|
|
f := T.Field(i)
|
2020-03-13 13:10:42 -06:00
|
|
|
fn(f)
|
2019-06-27 11:50:01 -06:00
|
|
|
if f.Anonymous() {
|
2020-03-13 13:10:42 -06:00
|
|
|
if seen == nil {
|
|
|
|
// Lazily create "seen" since it is only needed for
|
|
|
|
// embedded structs.
|
|
|
|
seen = make(map[*types.Struct]bool)
|
|
|
|
}
|
|
|
|
seen[T] = true
|
2019-06-27 11:50:01 -06:00
|
|
|
visit(f.Type())
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
visit(T)
|
|
|
|
}
|
|
|
|
|
2019-12-06 10:59:56 -07:00
|
|
|
// typeIsValid reports whether typ doesn't contain any Invalid types.
|
|
|
|
func typeIsValid(typ types.Type) bool {
|
2020-01-19 07:18:24 -07:00
|
|
|
// Check named types separately, because we don't want
|
|
|
|
// to call Underlying() on them to avoid problems with recursive types.
|
|
|
|
if _, ok := typ.(*types.Named); ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-12-06 10:59:56 -07:00
|
|
|
switch typ := typ.Underlying().(type) {
|
|
|
|
case *types.Basic:
|
|
|
|
return typ.Kind() != types.Invalid
|
|
|
|
case *types.Array:
|
|
|
|
return typeIsValid(typ.Elem())
|
|
|
|
case *types.Slice:
|
|
|
|
return typeIsValid(typ.Elem())
|
|
|
|
case *types.Pointer:
|
|
|
|
return typeIsValid(typ.Elem())
|
|
|
|
case *types.Map:
|
|
|
|
return typeIsValid(typ.Key()) && typeIsValid(typ.Elem())
|
|
|
|
case *types.Chan:
|
|
|
|
return typeIsValid(typ.Elem())
|
|
|
|
case *types.Signature:
|
|
|
|
return typeIsValid(typ.Params()) && typeIsValid(typ.Results())
|
|
|
|
case *types.Tuple:
|
|
|
|
for i := 0; i < typ.Len(); i++ {
|
|
|
|
if !typeIsValid(typ.At(i).Type()) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
2020-01-19 07:18:24 -07:00
|
|
|
case *types.Struct, *types.Interface:
|
|
|
|
// Don't bother checking structs, interfaces for validity.
|
2019-12-06 10:59:56 -07:00
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// resolveInvalid traverses the node of the AST that defines the scope
|
|
|
|
// containing the declaration of obj, and attempts to find a user-friendly
|
|
|
|
// name for its invalid type. The resulting Object and its Type are fake.
|
2019-12-06 10:59:56 -07:00
|
|
|
func resolveInvalid(fset *token.FileSet, obj types.Object, node ast.Node, info *types.Info) types.Object {
|
2019-04-24 17:26:34 -06:00
|
|
|
var resultExpr ast.Expr
|
|
|
|
ast.Inspect(node, func(node ast.Node) bool {
|
|
|
|
switch n := node.(type) {
|
|
|
|
case *ast.ValueSpec:
|
|
|
|
for _, name := range n.Names {
|
|
|
|
if info.Defs[name] == obj {
|
|
|
|
resultExpr = n.Type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
|
|
|
|
for _, name := range n.Names {
|
|
|
|
if info.Defs[name] == obj {
|
|
|
|
resultExpr = n.Type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
})
|
2019-12-06 10:59:56 -07:00
|
|
|
// Construct a fake type for the object and return a fake object with this type.
|
|
|
|
typename := formatNode(fset, resultExpr)
|
|
|
|
typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), types.Typ[types.Invalid], nil)
|
|
|
|
return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
|
|
|
|
2020-04-21 19:28:44 -06:00
|
|
|
func formatNode(fset *token.FileSet, n ast.Node) string {
|
|
|
|
var buf strings.Builder
|
|
|
|
if err := printer.Fprint(&buf, fset, n); err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
func isPointer(T types.Type) bool {
|
|
|
|
_, ok := T.(*types.Pointer)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-12-22 10:58:14 -07:00
|
|
|
func isVar(obj types.Object) bool {
|
|
|
|
_, ok := obj.(*types.Var)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-12-06 00:44:16 -07:00
|
|
|
// deref returns a pointer's element type, traversing as many levels as needed.
|
|
|
|
// Otherwise it returns typ.
|
2019-04-24 17:26:34 -06:00
|
|
|
func deref(typ types.Type) types.Type {
|
2019-12-06 00:44:16 -07:00
|
|
|
for {
|
|
|
|
p, ok := typ.Underlying().(*types.Pointer)
|
|
|
|
if !ok {
|
|
|
|
return typ
|
|
|
|
}
|
|
|
|
typ = p.Elem()
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
|
|
|
}
|
2019-04-29 19:08:16 -06:00
|
|
|
|
2019-06-17 12:11:13 -06:00
|
|
|
func isTypeName(obj types.Object) bool {
|
|
|
|
_, ok := obj.(*types.TypeName)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-06-19 16:24:05 -06:00
|
|
|
func isFunc(obj types.Object) bool {
|
|
|
|
_, ok := obj.(*types.Func)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
internal/lsp: add literal completion candidates
Add support for literal completion candidates such as "[]int{}" or
"make([]int, 0)". We support both named and unnamed types. I used the
existing type matching logic, so, for example, if the expected type is
an interface, we will suggest literal candidates that implement the
interface.
The literal candidates have a lower score than normal matching
candidates, so they shouldn't be disruptive in cases where you don't
want a literal candidate.
This commit adds support for slice, array, struct, map, and channel
literal candidates since they are pretty similar. Functions will be
supported in a subsequent commit.
I also added support for setting a snippet's final tab stop. This is
useful if you want the cursor to end up somewhere other than the
character after the snippet.
Change-Id: Id3b74260fff4d61703989b422267021b00cec005
Reviewed-on: https://go-review.googlesource.com/c/tools/+/193698
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-09-04 17:12:37 -06:00
|
|
|
func isEmptyInterface(T types.Type) bool {
|
|
|
|
intf, _ := T.(*types.Interface)
|
|
|
|
return intf != nil && intf.NumMethods() == 0
|
|
|
|
}
|
|
|
|
|
2019-11-15 17:14:15 -07:00
|
|
|
func isUntyped(T types.Type) bool {
|
|
|
|
if basic, ok := T.(*types.Basic); ok {
|
|
|
|
return basic.Info()&types.IsUntyped > 0
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-01-27 11:04:02 -07:00
|
|
|
func isPkgName(obj types.Object) bool {
|
|
|
|
_, ok := obj.(*types.PkgName)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2020-02-02 22:21:07 -07:00
|
|
|
func isASTFile(n ast.Node) bool {
|
|
|
|
_, ok := n.(*ast.File)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
func deslice(T types.Type) types.Type {
|
|
|
|
if slice, ok := T.Underlying().(*types.Slice); ok {
|
|
|
|
return slice.Elem()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-15 10:26:11 -06:00
|
|
|
// isSelector returns the enclosing *ast.SelectorExpr when pos is in the
|
|
|
|
// selector.
|
|
|
|
func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr {
|
|
|
|
if len(path) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if sel, ok := path[0].(*ast.SelectorExpr); ok {
|
|
|
|
return sel
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := path[0].(*ast.Ident); ok && len(path) > 1 {
|
|
|
|
if sel, ok := path[1].(*ast.SelectorExpr); ok && pos >= sel.Sel.Pos() {
|
|
|
|
return sel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-27 10:52:40 -06:00
|
|
|
func enclosingValueSpec(path []ast.Node) *ast.ValueSpec {
|
2019-12-22 16:04:15 -07:00
|
|
|
for _, n := range path {
|
|
|
|
if vs, ok := n.(*ast.ValueSpec); ok {
|
|
|
|
return vs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-27 16:37:50 -06:00
|
|
|
// typeConversion returns the type being converted to if call is a type
|
|
|
|
// conversion expression.
|
|
|
|
func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
|
|
|
|
var ident *ast.Ident
|
|
|
|
switch expr := call.Fun.(type) {
|
|
|
|
case *ast.Ident:
|
|
|
|
ident = expr
|
|
|
|
case *ast.SelectorExpr:
|
|
|
|
ident = expr.Sel
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type conversion (e.g. "float64(foo)").
|
|
|
|
if fun, _ := info.ObjectOf(ident).(*types.TypeName); fun != nil {
|
|
|
|
return fun.Type()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-15 23:28:29 -07:00
|
|
|
// fieldsAccessible returns whether s has at least one field accessible by p.
|
|
|
|
func fieldsAccessible(s *types.Struct, p *types.Package) bool {
|
|
|
|
for i := 0; i < s.NumFields(); i++ {
|
|
|
|
f := s.Field(i)
|
|
|
|
if f.Exported() || f.Pkg() == p {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-03-31 21:53:42 -06:00
|
|
|
func SortDiagnostics(d []*Diagnostic) {
|
2019-11-20 23:24:43 -07:00
|
|
|
sort.Slice(d, func(i int, j int) bool {
|
|
|
|
return CompareDiagnostic(d[i], d[j]) < 0
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-03-31 21:53:42 -06:00
|
|
|
func CompareDiagnostic(a, b *Diagnostic) int {
|
2019-11-20 23:24:43 -07:00
|
|
|
if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
|
|
|
|
return r
|
|
|
|
}
|
2020-01-25 17:58:28 -07:00
|
|
|
if a.Source < b.Source {
|
|
|
|
return -1
|
|
|
|
}
|
2019-11-20 23:24:43 -07:00
|
|
|
if a.Message < b.Message {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
if a.Message == b.Message {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return 1
|
|
|
|
}
|
2020-01-10 15:37:29 -07:00
|
|
|
|
2020-02-25 21:28:00 -07:00
|
|
|
func findPosInPackage(v View, searchpkg Package, pos token.Pos) (ParseGoHandle, Package, error) {
|
2020-01-10 15:37:29 -07:00
|
|
|
tok := v.Session().Cache().FileSet().File(pos)
|
|
|
|
if tok == nil {
|
|
|
|
return nil, nil, errors.Errorf("no file for pos in package %s", searchpkg.ID())
|
|
|
|
}
|
2020-02-12 14:36:46 -07:00
|
|
|
uri := span.URIFromPath(tok.Name())
|
2020-01-10 15:37:29 -07:00
|
|
|
|
2020-06-05 15:30:52 -06:00
|
|
|
ph, pkg, err := FindFileInPackage(searchpkg, uri)
|
2020-01-10 15:37:29 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2020-02-25 21:28:00 -07:00
|
|
|
return ph, pkg, nil
|
2020-01-10 15:37:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func findMapperInPackage(v View, searchpkg Package, uri span.URI) (*protocol.ColumnMapper, error) {
|
2020-06-05 15:30:52 -06:00
|
|
|
ph, _, err := FindFileInPackage(searchpkg, uri)
|
2020-01-10 15:37:29 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-02-10 21:10:59 -07:00
|
|
|
_, _, m, _, err := ph.Cached()
|
2020-01-10 15:37:29 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
2020-02-06 13:43:39 -07:00
|
|
|
// FindFileInPackage finds uri in pkg or its dependencies.
|
|
|
|
func FindFileInPackage(pkg Package, uri span.URI) (ParseGoHandle, Package, error) {
|
2020-01-10 15:37:29 -07:00
|
|
|
queue := []Package{pkg}
|
|
|
|
seen := make(map[string]bool)
|
|
|
|
|
|
|
|
for len(queue) > 0 {
|
|
|
|
pkg := queue[0]
|
|
|
|
queue = queue[1:]
|
|
|
|
seen[pkg.ID()] = true
|
|
|
|
|
|
|
|
if f, err := pkg.File(uri); err == nil {
|
|
|
|
return f, pkg, nil
|
|
|
|
}
|
|
|
|
for _, dep := range pkg.Imports() {
|
|
|
|
if !seen[dep.ID()] {
|
|
|
|
queue = append(queue, dep)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
|
|
|
|
}
|
2020-02-28 22:03:44 -07:00
|
|
|
|
|
|
|
// prevStmt returns the statement that precedes the statement containing pos.
|
|
|
|
// For example:
|
|
|
|
//
|
|
|
|
// foo := 1
|
|
|
|
// bar(1 + 2<>)
|
|
|
|
//
|
|
|
|
// If "<>" is pos, prevStmt returns "foo := 1"
|
|
|
|
func prevStmt(pos token.Pos, path []ast.Node) ast.Stmt {
|
|
|
|
var blockLines []ast.Stmt
|
|
|
|
for i := 0; i < len(path) && blockLines == nil; i++ {
|
|
|
|
switch n := path[i].(type) {
|
|
|
|
case *ast.BlockStmt:
|
|
|
|
blockLines = n.List
|
|
|
|
case *ast.CommClause:
|
|
|
|
blockLines = n.Body
|
|
|
|
case *ast.CaseClause:
|
|
|
|
blockLines = n.Body
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := len(blockLines) - 1; i >= 0; i-- {
|
|
|
|
if blockLines[i].End() < pos {
|
|
|
|
return blockLines[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// formatZeroValue produces Go code representing the zero value of T.
|
|
|
|
func formatZeroValue(T types.Type, qf types.Qualifier) string {
|
|
|
|
switch u := T.Underlying().(type) {
|
|
|
|
case *types.Basic:
|
|
|
|
switch {
|
|
|
|
case u.Info()&types.IsNumeric > 0:
|
|
|
|
return "0"
|
|
|
|
case u.Info()&types.IsString > 0:
|
|
|
|
return `""`
|
|
|
|
case u.Info()&types.IsBoolean > 0:
|
|
|
|
return "false"
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unhandled basic type: %v", u))
|
|
|
|
}
|
|
|
|
case *types.Pointer, *types.Interface, *types.Chan, *types.Map, *types.Slice, *types.Signature:
|
|
|
|
return "nil"
|
|
|
|
default:
|
|
|
|
return types.TypeString(T, qf) + "{}"
|
|
|
|
}
|
|
|
|
}
|
2020-07-14 18:40:38 -06:00
|
|
|
|
2020-07-23 21:24:36 -06:00
|
|
|
// MarshalArgs encodes the given arguments to json.RawMessages. This function
|
2020-07-14 18:40:38 -06:00
|
|
|
// is used to construct arguments to a protocol.Command.
|
|
|
|
//
|
|
|
|
// Example usage:
|
|
|
|
//
|
|
|
|
// jsonArgs, err := EncodeArgs(1, "hello", true, StructuredArg{42, 12.6})
|
|
|
|
//
|
2020-07-23 21:24:36 -06:00
|
|
|
func MarshalArgs(args ...interface{}) ([]json.RawMessage, error) {
|
2020-07-14 18:40:38 -06:00
|
|
|
var out []json.RawMessage
|
|
|
|
for _, arg := range args {
|
|
|
|
argJSON, err := json.Marshal(arg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
out = append(out, argJSON)
|
|
|
|
}
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2020-07-23 21:24:36 -06:00
|
|
|
// UnmarshalArgs decodes the given json.RawMessages to the variables provided
|
|
|
|
// by args. Each element of args should be a pointer.
|
2020-07-14 18:40:38 -06:00
|
|
|
//
|
|
|
|
// Example usage:
|
|
|
|
//
|
|
|
|
// var (
|
|
|
|
// num int
|
|
|
|
// str string
|
|
|
|
// bul bool
|
|
|
|
// structured StructuredArg
|
|
|
|
// )
|
2020-07-23 21:24:36 -06:00
|
|
|
// err := UnmarshalArgs(args, &num, &str, &bul, &structured)
|
2020-07-14 18:40:38 -06:00
|
|
|
//
|
2020-07-23 21:24:36 -06:00
|
|
|
func UnmarshalArgs(jsonArgs []json.RawMessage, args ...interface{}) error {
|
2020-07-14 18:40:38 -06:00
|
|
|
if len(args) != len(jsonArgs) {
|
|
|
|
return fmt.Errorf("DecodeArgs: expected %d input arguments, got %d JSON arguments", len(args), len(jsonArgs))
|
|
|
|
}
|
|
|
|
for i, arg := range args {
|
|
|
|
if err := json.Unmarshal(jsonArgs[i], arg); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|