| // Copyright 2021 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 cache |
| |
| import ( |
| "context" |
| "go/ast" |
| "go/parser" |
| "go/token" |
| "go/types" |
| "strings" |
| |
| "golang.org/x/tools/gopls/internal/lsp/lsppos" |
| "golang.org/x/tools/gopls/internal/lsp/protocol" |
| "golang.org/x/tools/gopls/internal/lsp/source" |
| "golang.org/x/tools/internal/memoize" |
| ) |
| |
| // symbolize returns the result of symbolizing the file identified by fh, using a cache. |
| func (s *snapshot) symbolize(ctx context.Context, fh source.FileHandle) ([]source.Symbol, error) { |
| uri := fh.URI() |
| |
| s.mu.Lock() |
| entry, hit := s.symbolizeHandles.Get(uri) |
| s.mu.Unlock() |
| |
| type symbolizeResult struct { |
| symbols []source.Symbol |
| err error |
| } |
| |
| // Cache miss? |
| if !hit { |
| type symbolHandleKey source.Hash |
| key := symbolHandleKey(fh.FileIdentity().Hash) |
| promise, release := s.store.Promise(key, func(_ context.Context, arg interface{}) interface{} { |
| symbols, err := symbolizeImpl(arg.(*snapshot), fh) |
| return symbolizeResult{symbols, err} |
| }) |
| |
| entry = promise |
| |
| s.mu.Lock() |
| s.symbolizeHandles.Set(uri, entry, func(_, _ interface{}) { release() }) |
| s.mu.Unlock() |
| } |
| |
| // Await result. |
| v, err := s.awaitPromise(ctx, entry.(*memoize.Promise)) |
| if err != nil { |
| return nil, err |
| } |
| res := v.(symbolizeResult) |
| return res.symbols, res.err |
| } |
| |
| // symbolizeImpl reads and parses a file and extracts symbols from it. |
| // It may use a parsed file already present in the cache but |
| // otherwise does not populate the cache. |
| func symbolizeImpl(snapshot *snapshot, fh source.FileHandle) ([]source.Symbol, error) { |
| src, err := fh.Read() |
| if err != nil { |
| return nil, err |
| } |
| |
| var ( |
| file *ast.File |
| fileDesc *token.File |
| ) |
| |
| // If the file has already been fully parsed through the |
| // cache, we can just use the result. But we don't want to |
| // populate the cache after a miss. |
| snapshot.mu.Lock() |
| pgf, _ := snapshot.peekParseGoLocked(fh, source.ParseFull) |
| snapshot.mu.Unlock() |
| if pgf != nil { |
| file = pgf.File |
| fileDesc = pgf.Tok |
| } |
| |
| // Otherwise, we parse the file ourselves. Notably we don't use parseGo here, |
| // so that we can avoid parsing comments and can skip object resolution, |
| // which has a meaningful impact on performance. Neither comments nor objects |
| // are necessary for symbol construction. |
| if file == nil { |
| fset := token.NewFileSet() |
| file, err = parser.ParseFile(fset, fh.URI().Filename(), src, skipObjectResolution) |
| if file == nil { |
| return nil, err |
| } |
| fileDesc = fset.File(file.Package) |
| } |
| |
| w := &symbolWalker{ |
| mapper: lsppos.NewTokenMapper(src, fileDesc), |
| } |
| |
| w.fileDecls(file.Decls) |
| |
| return w.symbols, w.firstError |
| } |
| |
| type symbolWalker struct { |
| mapper *lsppos.TokenMapper // for computing positions |
| |
| symbols []source.Symbol |
| firstError error |
| } |
| |
| func (w *symbolWalker) atNode(node ast.Node, name string, kind protocol.SymbolKind, path ...*ast.Ident) { |
| var b strings.Builder |
| for _, ident := range path { |
| if ident != nil { |
| b.WriteString(ident.Name) |
| b.WriteString(".") |
| } |
| } |
| b.WriteString(name) |
| |
| rng, err := w.mapper.Range(node.Pos(), node.End()) |
| if err != nil { |
| w.error(err) |
| return |
| } |
| sym := source.Symbol{ |
| Name: b.String(), |
| Kind: kind, |
| Range: rng, |
| } |
| w.symbols = append(w.symbols, sym) |
| } |
| |
| func (w *symbolWalker) error(err error) { |
| if err != nil && w.firstError == nil { |
| w.firstError = err |
| } |
| } |
| |
| func (w *symbolWalker) fileDecls(decls []ast.Decl) { |
| for _, decl := range decls { |
| switch decl := decl.(type) { |
| case *ast.FuncDecl: |
| kind := protocol.Function |
| var recv *ast.Ident |
| if decl.Recv.NumFields() > 0 { |
| kind = protocol.Method |
| recv = unpackRecv(decl.Recv.List[0].Type) |
| } |
| w.atNode(decl.Name, decl.Name.Name, kind, recv) |
| case *ast.GenDecl: |
| for _, spec := range decl.Specs { |
| switch spec := spec.(type) { |
| case *ast.TypeSpec: |
| kind := guessKind(spec) |
| w.atNode(spec.Name, spec.Name.Name, kind) |
| w.walkType(spec.Type, spec.Name) |
| case *ast.ValueSpec: |
| for _, name := range spec.Names { |
| kind := protocol.Variable |
| if decl.Tok == token.CONST { |
| kind = protocol.Constant |
| } |
| w.atNode(name, name.Name, kind) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| func guessKind(spec *ast.TypeSpec) protocol.SymbolKind { |
| switch spec.Type.(type) { |
| case *ast.InterfaceType: |
| return protocol.Interface |
| case *ast.StructType: |
| return protocol.Struct |
| case *ast.FuncType: |
| return protocol.Function |
| } |
| return protocol.Class |
| } |
| |
| func unpackRecv(rtyp ast.Expr) *ast.Ident { |
| // Extract the receiver identifier. Lifted from go/types/resolver.go |
| L: |
| for { |
| switch t := rtyp.(type) { |
| case *ast.ParenExpr: |
| rtyp = t.X |
| case *ast.StarExpr: |
| rtyp = t.X |
| default: |
| break L |
| } |
| } |
| if name, _ := rtyp.(*ast.Ident); name != nil { |
| return name |
| } |
| return nil |
| } |
| |
| // walkType processes symbols related to a type expression. path is path of |
| // nested type identifiers to the type expression. |
| func (w *symbolWalker) walkType(typ ast.Expr, path ...*ast.Ident) { |
| switch st := typ.(type) { |
| case *ast.StructType: |
| for _, field := range st.Fields.List { |
| w.walkField(field, protocol.Field, protocol.Field, path...) |
| } |
| case *ast.InterfaceType: |
| for _, field := range st.Methods.List { |
| w.walkField(field, protocol.Interface, protocol.Method, path...) |
| } |
| } |
| } |
| |
| // walkField processes symbols related to the struct field or interface method. |
| // |
| // unnamedKind and namedKind are the symbol kinds if the field is resp. unnamed |
| // or named. path is the path of nested identifiers containing the field. |
| func (w *symbolWalker) walkField(field *ast.Field, unnamedKind, namedKind protocol.SymbolKind, path ...*ast.Ident) { |
| if len(field.Names) == 0 { |
| switch typ := field.Type.(type) { |
| case *ast.SelectorExpr: |
| // embedded qualified type |
| w.atNode(field, typ.Sel.Name, unnamedKind, path...) |
| default: |
| w.atNode(field, types.ExprString(field.Type), unnamedKind, path...) |
| } |
| } |
| for _, name := range field.Names { |
| w.atNode(name, name.Name, namedKind, path...) |
| w.walkType(field.Type, append(path, name)...) |
| } |
| } |