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

     1  // Copyright 2019 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/token"
    13  	"go/types"
    14  	"reflect"
    15  	"sort"
    16  	"strings"
    17  	"sync"
    18  
    19  	"golang.org/x/sync/errgroup"
    20  	"golang.org/x/tools/gopls/internal/cache"
    21  	"golang.org/x/tools/gopls/internal/cache/metadata"
    22  	"golang.org/x/tools/gopls/internal/cache/methodsets"
    23  	"golang.org/x/tools/gopls/internal/file"
    24  	"golang.org/x/tools/gopls/internal/protocol"
    25  	"golang.org/x/tools/gopls/internal/util/bug"
    26  	"golang.org/x/tools/gopls/internal/util/safetoken"
    27  	"golang.org/x/tools/internal/event"
    28  )
    29  
    30  // This file defines the new implementation of the 'implementation'
    31  // operator that does not require type-checker data structures for an
    32  // unbounded number of packages.
    33  //
    34  // TODO(adonovan):
    35  // - Audit to ensure robustness in face of type errors.
    36  // - Eliminate false positives due to 'tricky' cases of the global algorithm.
    37  // - Ensure we have test coverage of:
    38  //      type aliases
    39  //      nil, PkgName, Builtin (all errors)
    40  //      any (empty result)
    41  //      method of unnamed interface type (e.g. var x interface { f() })
    42  //        (the global algorithm may find implementations of this type
    43  //         but will not include it in the index.)
    44  
    45  // Implementation returns a new sorted array of locations of
    46  // declarations of types that implement (or are implemented by) the
    47  // type referred to at the given position.
    48  //
    49  // If the position denotes a method, the computation is applied to its
    50  // receiver type and then its corresponding methods are returned.
    51  func Implementation(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position) ([]protocol.Location, error) {
    52  	ctx, done := event.Start(ctx, "golang.Implementation")
    53  	defer done()
    54  
    55  	locs, err := implementations(ctx, snapshot, f, pp)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	// Sort and de-duplicate locations.
    61  	sort.Slice(locs, func(i, j int) bool {
    62  		return protocol.CompareLocation(locs[i], locs[j]) < 0
    63  	})
    64  	out := locs[:0]
    65  	for _, loc := range locs {
    66  		if len(out) == 0 || out[len(out)-1] != loc {
    67  			out = append(out, loc)
    68  		}
    69  	}
    70  	locs = out
    71  
    72  	return locs, nil
    73  }
    74  
    75  func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.Location, error) {
    76  	obj, pkg, err := implementsObj(ctx, snapshot, fh.URI(), pp)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	var localPkgs []*cache.Package
    82  	if obj.Pos().IsValid() { // no local package for error or error.Error
    83  		declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos())
    84  		// Type-check the declaring package (incl. variants) for use
    85  		// by the "local" search, which uses type information to
    86  		// enumerate all types within the package that satisfy the
    87  		// query type, even those defined local to a function.
    88  		declURI := protocol.URIFromPath(declPosn.Filename)
    89  		declMPs, err := snapshot.MetadataForFile(ctx, declURI)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  		metadata.RemoveIntermediateTestVariants(&declMPs)
    94  		if len(declMPs) == 0 {
    95  			return nil, fmt.Errorf("no packages for file %s", declURI)
    96  		}
    97  		ids := make([]PackageID, len(declMPs))
    98  		for i, mp := range declMPs {
    99  			ids[i] = mp.ID
   100  		}
   101  		localPkgs, err = snapshot.TypeCheck(ctx, ids...)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  	}
   106  
   107  	// Is the selected identifier a type name or method?
   108  	// (For methods, report the corresponding method names.)
   109  	var queryType types.Type
   110  	var queryMethodID string
   111  	switch obj := obj.(type) {
   112  	case *types.TypeName:
   113  		queryType = obj.Type()
   114  	case *types.Func:
   115  		// For methods, use the receiver type, which may be anonymous.
   116  		if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
   117  			queryType = recv.Type()
   118  			queryMethodID = obj.Id()
   119  		}
   120  	}
   121  	if queryType == nil {
   122  		return nil, bug.Errorf("%s is not a type or method", obj.Name()) // should have been handled by implementsObj
   123  	}
   124  
   125  	// Compute the method-set fingerprint used as a key to the global search.
   126  	key, hasMethods := methodsets.KeyOf(queryType)
   127  	if !hasMethods {
   128  		// A type with no methods yields an empty result.
   129  		// (No point reporting that every type satisfies 'any'.)
   130  		return nil, nil
   131  	}
   132  
   133  	// The global search needs to look at every package in the
   134  	// forward transitive closure of the workspace; see package
   135  	// ./methodsets.
   136  	//
   137  	// For now we do all the type checking before beginning the search.
   138  	// TODO(adonovan): opt: search in parallel topological order
   139  	// so that we can overlap index lookup with typechecking.
   140  	// I suspect a number of algorithms on the result of TypeCheck could
   141  	// be optimized by being applied as soon as each package is available.
   142  	globalMetas, err := snapshot.AllMetadata(ctx)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	metadata.RemoveIntermediateTestVariants(&globalMetas)
   147  	globalIDs := make([]PackageID, 0, len(globalMetas))
   148  
   149  	var pkgPath PackagePath
   150  	if obj.Pkg() != nil { // nil for error
   151  		pkgPath = PackagePath(obj.Pkg().Path())
   152  	}
   153  	for _, mp := range globalMetas {
   154  		if mp.PkgPath == pkgPath {
   155  			continue // declaring package is handled by local implementation
   156  		}
   157  		globalIDs = append(globalIDs, mp.ID)
   158  	}
   159  	indexes, err := snapshot.MethodSets(ctx, globalIDs...)
   160  	if err != nil {
   161  		return nil, fmt.Errorf("querying method sets: %v", err)
   162  	}
   163  
   164  	// Search local and global packages in parallel.
   165  	var (
   166  		group  errgroup.Group
   167  		locsMu sync.Mutex
   168  		locs   []protocol.Location
   169  	)
   170  	// local search
   171  	for _, localPkg := range localPkgs {
   172  		localPkg := localPkg
   173  		group.Go(func() error {
   174  			localLocs, err := localImplementations(ctx, snapshot, localPkg, queryType, queryMethodID)
   175  			if err != nil {
   176  				return err
   177  			}
   178  			locsMu.Lock()
   179  			locs = append(locs, localLocs...)
   180  			locsMu.Unlock()
   181  			return nil
   182  		})
   183  	}
   184  	// global search
   185  	for _, index := range indexes {
   186  		index := index
   187  		group.Go(func() error {
   188  			for _, res := range index.Search(key, queryMethodID) {
   189  				loc := res.Location
   190  				// Map offsets to protocol.Locations in parallel (may involve I/O).
   191  				group.Go(func() error {
   192  					ploc, err := offsetToLocation(ctx, snapshot, loc.Filename, loc.Start, loc.End)
   193  					if err != nil {
   194  						return err
   195  					}
   196  					locsMu.Lock()
   197  					locs = append(locs, ploc)
   198  					locsMu.Unlock()
   199  					return nil
   200  				})
   201  			}
   202  			return nil
   203  		})
   204  	}
   205  	if err := group.Wait(); err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	return locs, nil
   210  }
   211  
   212  // offsetToLocation converts an offset-based position to a protocol.Location,
   213  // which requires reading the file.
   214  func offsetToLocation(ctx context.Context, snapshot *cache.Snapshot, filename string, start, end int) (protocol.Location, error) {
   215  	uri := protocol.URIFromPath(filename)
   216  	fh, err := snapshot.ReadFile(ctx, uri)
   217  	if err != nil {
   218  		return protocol.Location{}, err // cancelled, perhaps
   219  	}
   220  	content, err := fh.Content()
   221  	if err != nil {
   222  		return protocol.Location{}, err // nonexistent or deleted ("can't happen")
   223  	}
   224  	m := protocol.NewMapper(uri, content)
   225  	return m.OffsetLocation(start, end)
   226  }
   227  
   228  // implementsObj returns the object to query for implementations, which is a
   229  // type name or method.
   230  //
   231  // The returned Package is the narrowest package containing ppos, which is the
   232  // package using the resulting obj but not necessarily the declaring package.
   233  func implementsObj(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, ppos protocol.Position) (types.Object, *cache.Package, error) {
   234  	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, uri)
   235  	if err != nil {
   236  		return nil, nil, err
   237  	}
   238  	pos, err := pgf.PositionPos(ppos)
   239  	if err != nil {
   240  		return nil, nil, err
   241  	}
   242  
   243  	// This function inherits the limitation of its predecessor in
   244  	// requiring the selection to be an identifier (of a type or
   245  	// method). But there's no fundamental reason why one could
   246  	// not pose this query about any selected piece of syntax that
   247  	// has a type and thus a method set.
   248  	// (If LSP was more thorough about passing text selections as
   249  	// intervals to queries, you could ask about the method set of a
   250  	// subexpression such as x.f().)
   251  
   252  	// TODO(adonovan): simplify: use objectsAt?
   253  	path := pathEnclosingObjNode(pgf.File, pos)
   254  	if path == nil {
   255  		return nil, nil, ErrNoIdentFound
   256  	}
   257  	id, ok := path[0].(*ast.Ident)
   258  	if !ok {
   259  		return nil, nil, ErrNoIdentFound
   260  	}
   261  
   262  	// Is the object a type or method? Reject other kinds.
   263  	obj := pkg.GetTypesInfo().Uses[id]
   264  	if obj == nil {
   265  		// Check uses first (unlike ObjectOf) so that T in
   266  		// struct{T} is treated as a reference to a type,
   267  		// not a declaration of a field.
   268  		obj = pkg.GetTypesInfo().Defs[id]
   269  	}
   270  	switch obj := obj.(type) {
   271  	case *types.TypeName:
   272  		// ok
   273  	case *types.Func:
   274  		if obj.Type().(*types.Signature).Recv() == nil {
   275  			return nil, nil, fmt.Errorf("%s is a function, not a method", id.Name)
   276  		}
   277  	case nil:
   278  		return nil, nil, fmt.Errorf("%s denotes unknown object", id.Name)
   279  	default:
   280  		// e.g. *types.Var -> "var".
   281  		kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
   282  		return nil, nil, fmt.Errorf("%s is a %s, not a type", id.Name, kind)
   283  	}
   284  
   285  	return obj, pkg, nil
   286  }
   287  
   288  // localImplementations searches within pkg for declarations of all
   289  // types that are assignable to/from the query type, and returns a new
   290  // unordered array of their locations.
   291  //
   292  // If methodID is non-empty, the function instead returns the location
   293  // of each type's method (if any) of that ID.
   294  //
   295  // ("Local" refers to the search within the same package, but this
   296  // function's results may include type declarations that are local to
   297  // a function body. The global search index excludes such types
   298  // because reliably naming such types is hard.)
   299  func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, queryType types.Type, methodID string) ([]protocol.Location, error) {
   300  	queryType = methodsets.EnsurePointer(queryType)
   301  
   302  	// Scan through all type declarations in the syntax.
   303  	var locs []protocol.Location
   304  	var methodLocs []methodsets.Location
   305  	for _, pgf := range pkg.CompiledGoFiles() {
   306  		ast.Inspect(pgf.File, func(n ast.Node) bool {
   307  			spec, ok := n.(*ast.TypeSpec)
   308  			if !ok {
   309  				return true // not a type declaration
   310  			}
   311  			def := pkg.GetTypesInfo().Defs[spec.Name]
   312  			if def == nil {
   313  				return true // "can't happen" for types
   314  			}
   315  			if def.(*types.TypeName).IsAlias() {
   316  				return true // skip type aliases to avoid duplicate reporting
   317  			}
   318  			candidateType := methodsets.EnsurePointer(def.Type())
   319  
   320  			// The historical behavior enshrined by this
   321  			// function rejects cases where both are
   322  			// (nontrivial) interface types?
   323  			// That seems like useful information.
   324  			// TODO(adonovan): UX: report I/I pairs too?
   325  			// The same question appears in the global algorithm (methodsets).
   326  			if !concreteImplementsIntf(candidateType, queryType) {
   327  				return true // not assignable
   328  			}
   329  
   330  			// Ignore types with empty method sets.
   331  			// (No point reporting that every type satisfies 'any'.)
   332  			mset := types.NewMethodSet(candidateType)
   333  			if mset.Len() == 0 {
   334  				return true
   335  			}
   336  
   337  			if methodID == "" {
   338  				// Found matching type.
   339  				locs = append(locs, mustLocation(pgf, spec.Name))
   340  				return true
   341  			}
   342  
   343  			// Find corresponding method.
   344  			//
   345  			// We can't use LookupFieldOrMethod because it requires
   346  			// the methodID's types.Package, which we don't know.
   347  			// We could recursively search pkg.Imports for it,
   348  			// but it's easier to walk the method set.
   349  			for i := 0; i < mset.Len(); i++ {
   350  				method := mset.At(i).Obj()
   351  				if method.Id() == methodID {
   352  					posn := safetoken.StartPosition(pkg.FileSet(), method.Pos())
   353  					methodLocs = append(methodLocs, methodsets.Location{
   354  						Filename: posn.Filename,
   355  						Start:    posn.Offset,
   356  						End:      posn.Offset + len(method.Name()),
   357  					})
   358  					break
   359  				}
   360  			}
   361  			return true
   362  		})
   363  	}
   364  
   365  	// Finally convert method positions to protocol form by reading the files.
   366  	for _, mloc := range methodLocs {
   367  		loc, err := offsetToLocation(ctx, snapshot, mloc.Filename, mloc.Start, mloc.End)
   368  		if err != nil {
   369  			return nil, err
   370  		}
   371  		locs = append(locs, loc)
   372  	}
   373  
   374  	// Special case: for types that satisfy error, report builtin.go (see #59527).
   375  	if types.Implements(queryType, errorInterfaceType) {
   376  		loc, err := errorLocation(ctx, snapshot)
   377  		if err != nil {
   378  			return nil, err
   379  		}
   380  		locs = append(locs, loc)
   381  	}
   382  
   383  	return locs, nil
   384  }
   385  
   386  var errorInterfaceType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
   387  
   388  // errorLocation returns the location of the 'error' type in builtin.go.
   389  func errorLocation(ctx context.Context, snapshot *cache.Snapshot) (protocol.Location, error) {
   390  	pgf, err := snapshot.BuiltinFile(ctx)
   391  	if err != nil {
   392  		return protocol.Location{}, err
   393  	}
   394  	for _, decl := range pgf.File.Decls {
   395  		if decl, ok := decl.(*ast.GenDecl); ok {
   396  			for _, spec := range decl.Specs {
   397  				if spec, ok := spec.(*ast.TypeSpec); ok && spec.Name.Name == "error" {
   398  					return pgf.NodeLocation(spec.Name)
   399  				}
   400  			}
   401  		}
   402  	}
   403  	return protocol.Location{}, fmt.Errorf("built-in error type not found")
   404  }
   405  
   406  // concreteImplementsIntf returns true if a is an interface type implemented by
   407  // concrete type b, or vice versa.
   408  func concreteImplementsIntf(a, b types.Type) bool {
   409  	aIsIntf, bIsIntf := types.IsInterface(a), types.IsInterface(b)
   410  
   411  	// Make sure exactly one is an interface type.
   412  	if aIsIntf == bIsIntf {
   413  		return false
   414  	}
   415  
   416  	// Rearrange if needed so "a" is the concrete type.
   417  	if aIsIntf {
   418  		a, b = b, a
   419  	}
   420  
   421  	// TODO(adonovan): this should really use GenericAssignableTo
   422  	// to report (e.g.) "ArrayList[T] implements List[T]", but
   423  	// GenericAssignableTo doesn't work correctly on pointers to
   424  	// generic named types. Thus the legacy implementation and the
   425  	// "local" part of implementations fail to report generics.
   426  	// The global algorithm based on subsets does the right thing.
   427  	return types.AssignableTo(a, b)
   428  }
   429  
   430  var (
   431  	// TODO(adonovan): why do various RPC handlers related to
   432  	// IncomingCalls return (nil, nil) on the protocol in response
   433  	// to this error? That seems like a violation of the protocol.
   434  	// Is it perhaps a workaround for VSCode behavior?
   435  	errNoObjectFound = errors.New("no object found")
   436  )
   437  
   438  // pathEnclosingObjNode returns the AST path to the object-defining
   439  // node associated with pos. "Object-defining" means either an
   440  // *ast.Ident mapped directly to a types.Object or an ast.Node mapped
   441  // implicitly to a types.Object.
   442  func pathEnclosingObjNode(f *ast.File, pos token.Pos) []ast.Node {
   443  	var (
   444  		path  []ast.Node
   445  		found bool
   446  	)
   447  
   448  	ast.Inspect(f, func(n ast.Node) bool {
   449  		if found {
   450  			return false
   451  		}
   452  
   453  		if n == nil {
   454  			path = path[:len(path)-1]
   455  			return false
   456  		}
   457  
   458  		path = append(path, n)
   459  
   460  		switch n := n.(type) {
   461  		case *ast.Ident:
   462  			// Include the position directly after identifier. This handles
   463  			// the common case where the cursor is right after the
   464  			// identifier the user is currently typing. Previously we
   465  			// handled this by calling astutil.PathEnclosingInterval twice,
   466  			// once for "pos" and once for "pos-1".
   467  			found = n.Pos() <= pos && pos <= n.End()
   468  		case *ast.ImportSpec:
   469  			if n.Path.Pos() <= pos && pos < n.Path.End() {
   470  				found = true
   471  				// If import spec has a name, add name to path even though
   472  				// position isn't in the name.
   473  				if n.Name != nil {
   474  					path = append(path, n.Name)
   475  				}
   476  			}
   477  		case *ast.StarExpr:
   478  			// Follow star expressions to the inner identifier.
   479  			if pos == n.Star {
   480  				pos = n.X.Pos()
   481  			}
   482  		}
   483  
   484  		return !found
   485  	})
   486  
   487  	if len(path) == 0 {
   488  		return nil
   489  	}
   490  
   491  	// Reverse path so leaf is first element.
   492  	for i := 0; i < len(path)/2; i++ {
   493  		path[i], path[len(path)-1-i] = path[len(path)-1-i], path[i]
   494  	}
   495  
   496  	return path
   497  }