1
0
mirror of https://github.com/golang/go synced 2024-10-01 06:18:31 -06:00
go/internal/lsp/source/identifier.go
Rebecca Stambler 57610eddc9 internal/lsp: rework snapshots and cache FileHandles per-snapshot
This change does not complete the work to handle snapshots correctly,
but it does implement the behavior of re-building the snapshot on each
file invalidation.

It also moves to the approach of caching the FileHandles on the snapshot,
rather than in the goFile object, which is now not necessary.

Finally, this change shifts the logic of metadata invalidation into the
content invalidation step, so there is less logic to decide if we should
re-load a package or not.

Change-Id: I18387c385fb070da4db1302bf97035ce6328b5c3
Reviewed-on: https://go-review.googlesource.com/c/tools/+/197799
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-10-01 16:26:22 +00:00

362 lines
10 KiB
Go

// Copyright 2018 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 (
"context"
"go/ast"
"go/token"
"go/types"
"strconv"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/trace"
errors "golang.org/x/xerrors"
)
// IdentifierInfo holds information about an identifier in Go source.
type IdentifierInfo struct {
Name string
View View
snapshot Snapshot
File ParseGoHandle
mappedRange
Type struct {
mappedRange
Object types.Object
}
Declaration Declaration
pkg Package
ident *ast.Ident
wasEmbeddedField bool
qf types.Qualifier
}
type Declaration struct {
mappedRange
node ast.Node
obj types.Object
wasImplicit bool
}
// Identifier returns identifier information for a position
// in a file, accounting for a potentially incomplete selector.
func Identifier(ctx context.Context, view View, f File, pos protocol.Position) (*IdentifierInfo, error) {
snapshot, cphs, err := view.CheckPackageHandles(ctx, f)
if err != nil {
return nil, err
}
cph := WidestCheckPackageHandle(cphs)
pkg, err := cph.Check(ctx)
if err != nil {
return nil, err
}
ph, err := pkg.File(f.URI())
if err != nil {
return nil, err
}
file, m, _, err := ph.Cached(ctx)
if err != nil {
return nil, err
}
spn, err := m.PointSpan(pos)
if err != nil {
return nil, err
}
rng, err := spn.Range(m.Converter)
if err != nil {
return nil, err
}
return findIdentifier(ctx, view, snapshot, pkg, file, rng.Start)
}
func findIdentifier(ctx context.Context, view View, snapshot Snapshot, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
if result, err := identifier(ctx, view, snapshot, pkg, file, pos); err != nil || result != nil {
return result, err
}
// If the position is not an identifier but immediately follows
// an identifier or selector period (as is common when
// requesting a completion), use the path to the preceding node.
ident, err := identifier(ctx, view, snapshot, pkg, file, pos-1)
if ident == nil && err == nil {
err = errors.New("no identifier found")
}
return ident, err
}
// identifier checks a single position for a potential identifier.
func identifier(ctx context.Context, view View, snapshot Snapshot, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
ctx, done := trace.StartSpan(ctx, "source.identifier")
defer done()
var err error
// Handle import specs separately, as there is no formal position for a package declaration.
if result, err := importSpec(ctx, view, snapshot, file, pkg, pos); result != nil || err != nil {
return result, err
}
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
if path == nil {
return nil, errors.Errorf("can't find node enclosing position")
}
uri := span.FileURI(view.Session().Cache().FileSet().Position(pos).Filename)
var ph ParseGoHandle
for _, h := range pkg.Files() {
if h.File().Identity().URI == uri {
ph = h
}
}
result := &IdentifierInfo{
View: view,
snapshot: snapshot,
File: ph,
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
pkg: pkg,
ident: searchForIdent(path[0]),
}
// No identifier at the given position.
if result.ident == nil {
return nil, nil
}
for _, n := range path[1:] {
if field, ok := n.(*ast.Field); ok {
result.wasEmbeddedField = len(field.Names) == 0
break
}
}
result.Name = result.ident.Name
if result.mappedRange, err = posToMappedRange(ctx, view, pkg, result.ident.Pos(), result.ident.End()); err != nil {
return nil, err
}
result.Declaration.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
if result.Declaration.obj == nil {
// If there was no types.Object for the declaration, there might be an implicit local variable
// declaration in a type switch.
if objs := typeSwitchVar(pkg.GetTypesInfo(), path); len(objs) > 0 {
// There is no types.Object for the declaration of an implicit local variable,
// but all of the types.Objects associated with the usages of this variable can be
// used to connect it back to the declaration.
// Preserve the first of these objects and treat it as if it were the declaring object.
result.Declaration.obj = objs[0]
result.Declaration.wasImplicit = true
} else {
// Probably a type error.
return nil, errors.Errorf("no object for ident %v", result.Name)
}
}
// Handle builtins separately.
if result.Declaration.obj.Parent() == types.Universe {
obj := view.BuiltinPackage().Lookup(result.Name)
if obj == nil {
return result, nil
}
decl, ok := obj.Decl.(ast.Node)
if !ok {
return nil, errors.Errorf("no declaration for %s", result.Name)
}
result.Declaration.node = decl
if result.Declaration.mappedRange, err = nameToMappedRange(ctx, view, pkg, decl.Pos(), result.Name); err != nil {
return nil, err
}
return result, nil
}
if result.wasEmbeddedField {
// The original position was on the embedded field declaration, so we
// try to dig out the type and jump to that instead.
if v, ok := result.Declaration.obj.(*types.Var); ok {
if typObj := typeToObject(v.Type()); typObj != nil {
result.Declaration.obj = typObj
}
}
}
for _, obj := range pkg.GetTypesInfo().Implicits {
if obj.Pos() == result.Declaration.obj.Pos() {
// Mark this declaration as implicit, since it will not
// appear in a (*types.Info).Defs map.
result.Declaration.wasImplicit = true
break
}
}
if result.Declaration.mappedRange, err = objToMappedRange(ctx, view, pkg, result.Declaration.obj); err != nil {
return nil, err
}
if result.Declaration.node, err = objToNode(ctx, view, pkg, result.Declaration.obj); err != nil {
return nil, err
}
typ := pkg.GetTypesInfo().TypeOf(result.ident)
if typ == nil {
return result, nil
}
result.Type.Object = typeToObject(typ)
if result.Type.Object != nil {
// Identifiers with the type "error" are a special case with no position.
if hasErrorType(result.Type.Object) {
return result, nil
}
if result.Type.mappedRange, err = objToMappedRange(ctx, view, pkg, result.Type.Object); err != nil {
return nil, err
}
}
return result, nil
}
func searchForIdent(n ast.Node) *ast.Ident {
switch node := n.(type) {
case *ast.Ident:
return node
case *ast.SelectorExpr:
return node.Sel
case *ast.StarExpr:
return searchForIdent(node.X)
}
return nil
}
func typeToObject(typ types.Type) types.Object {
switch typ := typ.(type) {
case *types.Named:
return typ.Obj()
case *types.Pointer:
return typeToObject(typ.Elem())
default:
return nil
}
}
func hasErrorType(obj types.Object) bool {
return types.IsInterface(obj.Type()) && obj.Pkg() == nil && obj.Name() == "error"
}
func objToNode(ctx context.Context, view View, pkg Package, obj types.Object) (ast.Decl, error) {
uri := span.FileURI(view.Session().Cache().FileSet().Position(obj.Pos()).Filename)
ph, _, err := pkg.FindFile(ctx, uri)
if err != nil {
return nil, err
}
declAST, _, _, err := ph.Cached(ctx)
if declAST == nil {
return nil, err
}
if !(declAST.Pos() <= obj.Pos() && obj.Pos() <= declAST.End()) {
return nil, errors.Errorf("no file for %s", obj.Name())
}
path, _ := astutil.PathEnclosingInterval(declAST, obj.Pos(), obj.Pos())
if path == nil {
return nil, errors.Errorf("no path for object %v", obj.Name())
}
for _, node := range path {
switch node := node.(type) {
case *ast.GenDecl:
// Type names, fields, and methods.
switch obj.(type) {
case *types.TypeName, *types.Var, *types.Const, *types.Func:
return node, nil
}
case *ast.FuncDecl:
// Function signatures.
if _, ok := obj.(*types.Func); ok {
return node, nil
}
}
}
return nil, nil // didn't find a node, but don't fail
}
// importSpec handles positions inside of an *ast.ImportSpec.
func importSpec(ctx context.Context, view View, snapshot Snapshot, file *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
var imp *ast.ImportSpec
for _, spec := range file.Imports {
if spec.Path.Pos() <= pos && pos < spec.Path.End() {
imp = spec
}
}
if imp == nil {
return nil, nil
}
importPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
return nil, errors.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
}
uri := span.FileURI(view.Session().Cache().FileSet().Position(pos).Filename)
var ph ParseGoHandle
for _, h := range pkg.Files() {
if h.File().Identity().URI == uri {
ph = h
}
}
result := &IdentifierInfo{
View: view,
snapshot: snapshot,
File: ph,
Name: importPath,
pkg: pkg,
}
if result.mappedRange, err = posToMappedRange(ctx, view, pkg, imp.Path.Pos(), imp.Path.End()); err != nil {
return nil, err
}
// Consider the "declaration" of an import spec to be the imported package.
importedPkg, err := pkg.GetImport(ctx, importPath)
if err != nil {
return nil, err
}
if importedPkg.GetSyntax(ctx) == nil {
return nil, errors.Errorf("no syntax for for %q", importPath)
}
// Heuristic: Jump to the longest (most "interesting") file of the package.
var dest *ast.File
for _, f := range importedPkg.GetSyntax(ctx) {
if dest == nil || f.End()-f.Pos() > dest.End()-dest.Pos() {
dest = f
}
}
if dest == nil {
return nil, errors.Errorf("package %q has no files", importPath)
}
if result.Declaration.mappedRange, err = posToMappedRange(ctx, view, pkg, dest.Pos(), dest.End()); err != nil {
return nil, err
}
result.Declaration.node = imp
return result, nil
}
// typeSwitchVar handles the special case of a local variable implicitly defined in a type switch.
// In such cases, the definition of the implicit variable will not be recorded in the *types.Info.Defs map,
// but rather in the *types.Info.Implicits map.
func typeSwitchVar(info *types.Info, path []ast.Node) []types.Object {
if len(path) < 3 {
return nil
}
// Check for [Ident AssignStmt TypeSwitchStmt...]
if _, ok := path[0].(*ast.Ident); !ok {
return nil
}
if _, ok := path[1].(*ast.AssignStmt); !ok {
return nil
}
sw, ok := path[2].(*ast.TypeSwitchStmt)
if !ok {
return nil
}
var res []types.Object
for _, stmt := range sw.Body.List {
obj := info.Implicits[stmt.(*ast.CaseClause)]
if obj != nil {
res = append(res, obj)
}
}
return res
}