golang.org/x/tools/gopls@v0.15.3/internal/golang/definition.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package golang
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/parser"
    13  	"go/token"
    14  	"go/types"
    15  
    16  	"golang.org/x/tools/gopls/internal/cache"
    17  	"golang.org/x/tools/gopls/internal/cache/metadata"
    18  	"golang.org/x/tools/gopls/internal/cache/parsego"
    19  	"golang.org/x/tools/gopls/internal/file"
    20  	"golang.org/x/tools/gopls/internal/protocol"
    21  	"golang.org/x/tools/gopls/internal/util/bug"
    22  	"golang.org/x/tools/internal/event"
    23  )
    24  
    25  // Definition handles the textDocument/definition request for Go files.
    26  func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.Location, error) {
    27  	ctx, done := event.Start(ctx, "golang.Definition")
    28  	defer done()
    29  
    30  	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  	pos, err := pgf.PositionPos(position)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	// Handle the case where the cursor is in an import.
    40  	importLocations, err := importDefinition(ctx, snapshot, pkg, pgf, pos)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	if len(importLocations) > 0 {
    45  		return importLocations, nil
    46  	}
    47  
    48  	// Handle the case where the cursor is in the package name.
    49  	// We use "<= End" to accept a query immediately after the package name.
    50  	if pgf.File != nil && pgf.File.Name.Pos() <= pos && pos <= pgf.File.Name.End() {
    51  		// If there's no package documentation, just use current file.
    52  		declFile := pgf
    53  		for _, pgf := range pkg.CompiledGoFiles() {
    54  			if pgf.File.Name != nil && pgf.File.Doc != nil {
    55  				declFile = pgf
    56  				break
    57  			}
    58  		}
    59  		loc, err := declFile.NodeLocation(declFile.File.Name)
    60  		if err != nil {
    61  			return nil, err
    62  		}
    63  		return []protocol.Location{loc}, nil
    64  	}
    65  
    66  	// Handle the case where the cursor is in a linkname directive.
    67  	locations, err := LinknameDefinition(ctx, snapshot, pgf.Mapper, position)
    68  	if !errors.Is(err, ErrNoLinkname) {
    69  		return locations, err
    70  	}
    71  
    72  	// Handle the case where the cursor is in an embed directive.
    73  	locations, err = EmbedDefinition(pgf.Mapper, position)
    74  	if !errors.Is(err, ErrNoEmbed) {
    75  		return locations, err
    76  	}
    77  
    78  	// The general case: the cursor is on an identifier.
    79  	_, obj, _ := referencedObject(pkg, pgf, pos)
    80  	if obj == nil {
    81  		return nil, nil
    82  	}
    83  
    84  	// Handle objects with no position: builtin, unsafe.
    85  	if !obj.Pos().IsValid() {
    86  		return builtinDefinition(ctx, snapshot, obj)
    87  	}
    88  
    89  	// Finally, map the object position.
    90  	loc, err := mapPosition(ctx, pkg.FileSet(), snapshot, obj.Pos(), adjustedObjEnd(obj))
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  	return []protocol.Location{loc}, nil
    95  }
    96  
    97  // builtinDefinition returns the location of the fake source
    98  // declaration of a built-in in {builtin,unsafe}.go.
    99  func builtinDefinition(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) ([]protocol.Location, error) {
   100  	pgf, decl, err := builtinDecl(ctx, snapshot, obj)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	loc, err := pgf.PosLocation(decl.Pos(), decl.Pos()+token.Pos(len(obj.Name())))
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	return []protocol.Location{loc}, nil
   110  }
   111  
   112  // builtinDecl returns the parsed Go file and node corresponding to a builtin
   113  // object, which may be a universe object or part of types.Unsafe.
   114  func builtinDecl(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) (*parsego.File, ast.Node, error) {
   115  	// getDecl returns the file-level declaration of name
   116  	// using legacy (go/ast) object resolution.
   117  	getDecl := func(file *ast.File, name string) (ast.Node, error) {
   118  		astObj := file.Scope.Lookup(name)
   119  		if astObj == nil {
   120  			// Every built-in should have documentation syntax.
   121  			// However, it is possible to reach this statement by
   122  			// commenting out declarations in {builtin,unsafe}.go.
   123  			return nil, fmt.Errorf("internal error: no object for %s", name)
   124  		}
   125  		decl, ok := astObj.Decl.(ast.Node)
   126  		if !ok {
   127  			return nil, bug.Errorf("internal error: no declaration for %s", obj.Name())
   128  		}
   129  		return decl, nil
   130  	}
   131  
   132  	var (
   133  		pgf  *ParsedGoFile
   134  		decl ast.Node
   135  		err  error
   136  	)
   137  	if obj.Pkg() == types.Unsafe {
   138  		// package "unsafe":
   139  		// parse $GOROOT/src/unsafe/unsafe.go
   140  		unsafe := snapshot.Metadata("unsafe")
   141  		if unsafe == nil {
   142  			// If the type checker somehow resolved 'unsafe', we must have metadata
   143  			// for it.
   144  			return nil, nil, bug.Errorf("no metadata for package 'unsafe'")
   145  		}
   146  		uri := unsafe.GoFiles[0]
   147  		fh, err := snapshot.ReadFile(ctx, uri)
   148  		if err != nil {
   149  			return nil, nil, err
   150  		}
   151  		pgf, err = snapshot.ParseGo(ctx, fh, ParseFull&^parser.SkipObjectResolution)
   152  		if err != nil {
   153  			return nil, nil, err
   154  		}
   155  		decl, err = getDecl(pgf.File, obj.Name())
   156  		if err != nil {
   157  			return nil, nil, err
   158  		}
   159  	} else {
   160  		// pseudo-package "builtin":
   161  		// use parsed $GOROOT/src/builtin/builtin.go
   162  		pgf, err = snapshot.BuiltinFile(ctx)
   163  		if err != nil {
   164  			return nil, nil, err
   165  		}
   166  
   167  		if obj.Parent() == types.Universe {
   168  			// built-in function or type
   169  			decl, err = getDecl(pgf.File, obj.Name())
   170  			if err != nil {
   171  				return nil, nil, err
   172  			}
   173  		} else if obj.Name() == "Error" {
   174  			// error.Error method
   175  			decl, err = getDecl(pgf.File, "error")
   176  			if err != nil {
   177  				return nil, nil, err
   178  			}
   179  			decl = decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List[0]
   180  
   181  		} else {
   182  			return nil, nil, bug.Errorf("unknown built-in %v", obj)
   183  		}
   184  	}
   185  	return pgf, decl, nil
   186  }
   187  
   188  // referencedObject returns the identifier and object referenced at the
   189  // specified position, which must be within the file pgf, for the purposes of
   190  // definition/hover/call hierarchy operations. It returns a nil object if no
   191  // object was found at the given position.
   192  //
   193  // If the returned identifier is a type-switch implicit (i.e. the x in x :=
   194  // e.(type)), the third result will be the type of the expression being
   195  // switched on (the type of e in the example). This facilitates workarounds for
   196  // limitations of the go/types API, which does not report an object for the
   197  // identifier x.
   198  //
   199  // For embedded fields, referencedObject returns the type name object rather
   200  // than the var (field) object.
   201  //
   202  // TODO(rfindley): this function exists to preserve the pre-existing behavior
   203  // of golang.Identifier. Eliminate this helper in favor of sharing
   204  // functionality with objectsAt, after choosing suitable primitives.
   205  func referencedObject(pkg *cache.Package, pgf *ParsedGoFile, pos token.Pos) (*ast.Ident, types.Object, types.Type) {
   206  	path := pathEnclosingObjNode(pgf.File, pos)
   207  	if len(path) == 0 {
   208  		return nil, nil, nil
   209  	}
   210  	var obj types.Object
   211  	info := pkg.GetTypesInfo()
   212  	switch n := path[0].(type) {
   213  	case *ast.Ident:
   214  		obj = info.ObjectOf(n)
   215  		// If n is the var's declaring ident in a type switch
   216  		// [i.e. the x in x := foo.(type)], it will not have an object. In this
   217  		// case, set obj to the first implicit object (if any), and return the type
   218  		// of the expression being switched on.
   219  		//
   220  		// The type switch may have no case clauses and thus no
   221  		// implicit objects; this is a type error ("unused x"),
   222  		if obj == nil {
   223  			if implicits, typ := typeSwitchImplicits(info, path); len(implicits) > 0 {
   224  				return n, implicits[0], typ
   225  			}
   226  		}
   227  
   228  		// If the original position was an embedded field, we want to jump
   229  		// to the field's type definition, not the field's definition.
   230  		if v, ok := obj.(*types.Var); ok && v.Embedded() {
   231  			// types.Info.Uses contains the embedded field's *types.TypeName.
   232  			if typeName := info.Uses[n]; typeName != nil {
   233  				obj = typeName
   234  			}
   235  		}
   236  		return n, obj, nil
   237  	}
   238  	return nil, nil, nil
   239  }
   240  
   241  // importDefinition returns locations defining a package referenced by the
   242  // import spec containing pos.
   243  //
   244  // If pos is not inside an import spec, it returns nil, nil.
   245  func importDefinition(ctx context.Context, s *cache.Snapshot, pkg *cache.Package, pgf *ParsedGoFile, pos token.Pos) ([]protocol.Location, error) {
   246  	var imp *ast.ImportSpec
   247  	for _, spec := range pgf.File.Imports {
   248  		// We use "<= End" to accept a query immediately after an ImportSpec.
   249  		if spec.Path.Pos() <= pos && pos <= spec.Path.End() {
   250  			imp = spec
   251  		}
   252  	}
   253  	if imp == nil {
   254  		return nil, nil
   255  	}
   256  
   257  	importPath := metadata.UnquoteImportPath(imp)
   258  	impID := pkg.Metadata().DepsByImpPath[importPath]
   259  	if impID == "" {
   260  		return nil, fmt.Errorf("failed to resolve import %q", importPath)
   261  	}
   262  	impMetadata := s.Metadata(impID)
   263  	if impMetadata == nil {
   264  		return nil, fmt.Errorf("missing information for package %q", impID)
   265  	}
   266  
   267  	var locs []protocol.Location
   268  	for _, f := range impMetadata.CompiledGoFiles {
   269  		fh, err := s.ReadFile(ctx, f)
   270  		if err != nil {
   271  			if ctx.Err() != nil {
   272  				return nil, ctx.Err()
   273  			}
   274  			continue
   275  		}
   276  		pgf, err := s.ParseGo(ctx, fh, ParseHeader)
   277  		if err != nil {
   278  			if ctx.Err() != nil {
   279  				return nil, ctx.Err()
   280  			}
   281  			continue
   282  		}
   283  		loc, err := pgf.NodeLocation(pgf.File)
   284  		if err != nil {
   285  			return nil, err
   286  		}
   287  		locs = append(locs, loc)
   288  	}
   289  
   290  	if len(locs) == 0 {
   291  		return nil, fmt.Errorf("package %q has no readable files", impID) // incl. unsafe
   292  	}
   293  
   294  	return locs, nil
   295  }
   296  
   297  // TODO(rfindley): avoid the duplicate column mapping here, by associating a
   298  // column mapper with each file handle.
   299  func mapPosition(ctx context.Context, fset *token.FileSet, s file.Source, start, end token.Pos) (protocol.Location, error) {
   300  	file := fset.File(start)
   301  	uri := protocol.URIFromPath(file.Name())
   302  	fh, err := s.ReadFile(ctx, uri)
   303  	if err != nil {
   304  		return protocol.Location{}, err
   305  	}
   306  	content, err := fh.Content()
   307  	if err != nil {
   308  		return protocol.Location{}, err
   309  	}
   310  	m := protocol.NewMapper(fh.URI(), content)
   311  	return m.PosLocation(file, start, end)
   312  }