| // Copyright 2025 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 goasm | |
| import ( | |
| "context" | |
| "go/types" | |
| "strings" | |
| "golang.org/x/tools/gopls/internal/cache" | |
| "golang.org/x/tools/gopls/internal/cache/metadata" | |
| "golang.org/x/tools/gopls/internal/file" | |
| "golang.org/x/tools/gopls/internal/protocol" | |
| "golang.org/x/tools/gopls/internal/util/asm" | |
| ) | |
| // A resolution is the result of resolving an assembly identifier to its | |
| // definition, shared by Definition and Hover. | |
| type resolution struct { | |
| // file is the parsed assembly file. | |
| file *asm.File | |
| // found is the identifier under the cursor, or nil if the cursor is | |
| // not on an identifier. | |
| found *asm.Ident | |
| // obj is the Go object for a package-qualified symbol (including a | |
| // current-package symbol such as ·foo), or nil if the symbol has no | |
| // Go declaration. | |
| obj types.Object | |
| // pkg is the type-checked package that declares obj, or nil. | |
| pkg *cache.Package | |
| // localDef is the defining identifier in the assembly file for a local | |
| // symbol — a label, a bare TEXT/GLOBL symbol, or a current-package | |
| // symbol without a Go declaration. It is nil if none was found. For a | |
| // label, only the enclosing TEXT function is searched. | |
| localDef *asm.Ident | |
| } | |
| // resolve resolves the assembly identifier at rng to its definition. | |
| // | |
| // For a package-qualified symbol (including a current-package symbol such | |
| // as ·foo, which is rewritten to pkgpath.foo), resolve type-checks the | |
| // declaring package and returns the Go object in res.obj. For a local | |
| // symbol, resolve returns the defining identifier in the assembly file in | |
| // res.localDef. | |
| // | |
| // res.found is nil if the cursor is not on an identifier. | |
| func resolve(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) (res resolution, err error) { | |
| // Package metadata is needed only to resolve package-qualified symbols | |
| // to Go declarations. An assembly-only file with no Go package has | |
| // none; tolerate the error and fall back to local assembly definitions. | |
| mp, err := snapshot.NarrowestMetadataForFile(ctx, fh.URI()) | |
| if err != nil { | |
| mp = nil | |
| } | |
| content, err := fh.Content() | |
| if err != nil { | |
| return res, err | |
| } | |
| // TODO(adonovan): make this just another | |
| // attribute of the type-checked cache.Package. | |
| res.file = asm.Parse(fh.URI(), content) | |
| start, end, err := res.file.Mapper.RangeOffsets(rng) | |
| if err != nil { | |
| return res, err | |
| } | |
| // Find the identifier under the cursor. | |
| // Use the selection range so that haphazard selections that | |
| // happen to start in an identifier don't produce spurious matches. | |
| res.found = res.file.IdentAt(start, end) | |
| if res.found == nil { | |
| return res, nil | |
| } | |
| // Resolve a package-qualified symbol (including a current-package | |
| // symbol such as ·foo) to its Go declaration. | |
| if mp != nil { | |
| sym := res.found.Name | |
| if sym != "" && sym[0] == '.' { | |
| sym = string(mp.PkgPath) + sym | |
| } | |
| if pkgpath, name, ok := strings.CutLast(sym, "."); ok { | |
| // Find declaring package among dependencies. | |
| // | |
| // TODO(adonovan): assembly may legally reference | |
| // non-dependencies. For example, sync/atomic calls | |
| // internal/runtime/atomic. Perhaps we should search | |
| // the entire metadata graph, but that's path-dependent. | |
| var declaring *metadata.Package | |
| for pkg := range snapshot.MetadataGraph().ForwardReflexiveTransitiveClosure(mp.ID) { | |
| if pkg.PkgPath == metadata.PackagePath(pkgpath) { | |
| declaring = pkg | |
| break | |
| } | |
| } | |
| if declaring != nil { | |
| pkgs, err := snapshot.TypeCheck(ctx, declaring.ID) | |
| if err != nil { | |
| return res, err | |
| } | |
| res.pkg = pkgs[0] | |
| res.obj = res.pkg.Types().Scope().Lookup(name) | |
| } | |
| // If obj is nil (no Go declaration, e.g. an asm-only | |
| // symbol), fall through to the local-definition search. | |
| } | |
| } | |
| // Find the definition of a local symbol — a label, a bare TEXT/GLOBL | |
| // symbol, or a package-qualified symbol without a Go declaration — in | |
| // the assembly file. | |
| if res.obj == nil { | |
| // Labels are function-scoped: a label definition matches only | |
| // within the enclosing TEXT function, so that a jump doesn't land | |
| // on a same-named label in another function. | |
| lo, hi := res.file.FunctionRange(res.found.Offset) | |
| for _, id := range res.file.Idents { | |
| if id.Name != res.found.Name { | |
| continue | |
| } | |
| switch id.Kind { | |
| case asm.Text, asm.Global: | |
| res.localDef = &id | |
| case asm.Label: | |
| if lo <= id.Offset && id.Offset < hi { | |
| res.localDef = &id | |
| } | |
| } | |
| if res.localDef != nil { | |
| break | |
| } | |
| } | |
| } | |
| return res, nil | |
| } |