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 }