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

     1  // Copyright 2018 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  	"fmt"
    10  	"go/ast"
    11  	"go/token"
    12  	"go/types"
    13  	"strings"
    14  
    15  	"golang.org/x/tools/go/ast/astutil"
    16  	"golang.org/x/tools/gopls/internal/cache"
    17  	"golang.org/x/tools/gopls/internal/file"
    18  	"golang.org/x/tools/gopls/internal/protocol"
    19  	"golang.org/x/tools/gopls/internal/settings"
    20  	"golang.org/x/tools/gopls/internal/util/bug"
    21  	"golang.org/x/tools/gopls/internal/util/typesutil"
    22  	"golang.org/x/tools/internal/event"
    23  )
    24  
    25  func SignatureHelp(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.SignatureInformation, int, error) {
    26  	ctx, done := event.Start(ctx, "golang.SignatureHelp")
    27  	defer done()
    28  
    29  	// We need full type-checking here, as we must type-check function bodies in
    30  	// order to provide signature help at the requested position.
    31  	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    32  	if err != nil {
    33  		return nil, 0, fmt.Errorf("getting file for SignatureHelp: %w", err)
    34  	}
    35  	pos, err := pgf.PositionPos(position)
    36  	if err != nil {
    37  		return nil, 0, err
    38  	}
    39  	// Find a call expression surrounding the query position.
    40  	var callExpr *ast.CallExpr
    41  	path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
    42  	if path == nil {
    43  		return nil, 0, fmt.Errorf("cannot find node enclosing position")
    44  	}
    45  FindCall:
    46  	for _, node := range path {
    47  		switch node := node.(type) {
    48  		case *ast.CallExpr:
    49  			if pos >= node.Lparen && pos <= node.Rparen {
    50  				callExpr = node
    51  				break FindCall
    52  			}
    53  		case *ast.FuncLit, *ast.FuncType:
    54  			// The user is within an anonymous function,
    55  			// which may be the parameter to the *ast.CallExpr.
    56  			// Don't show signature help in this case.
    57  			return nil, 0, fmt.Errorf("no signature help within a function declaration")
    58  		case *ast.BasicLit:
    59  			if node.Kind == token.STRING {
    60  				return nil, 0, fmt.Errorf("no signature help within a string literal")
    61  			}
    62  		}
    63  
    64  	}
    65  	if callExpr == nil || callExpr.Fun == nil {
    66  		return nil, 0, fmt.Errorf("cannot find an enclosing function")
    67  	}
    68  
    69  	info := pkg.GetTypesInfo()
    70  
    71  	// Get the type information for the function being called.
    72  	var sig *types.Signature
    73  	if tv, ok := info.Types[callExpr.Fun]; !ok {
    74  		return nil, 0, fmt.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun)
    75  	} else if tv.IsType() {
    76  		return nil, 0, fmt.Errorf("this is a conversion to %s, not a call", tv.Type)
    77  	} else if sig, ok = tv.Type.Underlying().(*types.Signature); !ok {
    78  		return nil, 0, fmt.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun)
    79  	}
    80  	// Inv: sig != nil
    81  
    82  	qf := typesutil.FileQualifier(pgf.File, pkg.GetTypes(), info)
    83  
    84  	// Get the object representing the function, if available.
    85  	// There is no object in certain cases such as calling a function returned by
    86  	// a function (e.g. "foo()()").
    87  	var obj types.Object
    88  	switch t := callExpr.Fun.(type) {
    89  	case *ast.Ident:
    90  		obj = info.ObjectOf(t)
    91  	case *ast.SelectorExpr:
    92  		obj = info.ObjectOf(t.Sel)
    93  	}
    94  
    95  	// Call to built-in?
    96  	if obj != nil && !obj.Pos().IsValid() {
    97  		// function?
    98  		if obj, ok := obj.(*types.Builtin); ok {
    99  			return builtinSignature(ctx, snapshot, callExpr, obj.Name(), pos)
   100  		}
   101  
   102  		// method (only error.Error)?
   103  		if fn, ok := obj.(*types.Func); ok && fn.Name() == "Error" {
   104  			return &protocol.SignatureInformation{
   105  				Label:         "Error()",
   106  				Documentation: stringToSigInfoDocumentation("Error returns the error message.", snapshot.Options()),
   107  			}, 0, nil
   108  		}
   109  
   110  		return nil, 0, bug.Errorf("call to unexpected built-in %v (%T)", obj, obj)
   111  	}
   112  
   113  	activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), pos)
   114  
   115  	var (
   116  		name    string
   117  		comment *ast.CommentGroup
   118  	)
   119  	if obj != nil {
   120  		d, err := HoverDocForObject(ctx, snapshot, pkg.FileSet(), obj)
   121  		if err != nil {
   122  			return nil, 0, err
   123  		}
   124  		name = obj.Name()
   125  		comment = d
   126  	} else {
   127  		name = "func"
   128  	}
   129  	mq := MetadataQualifierForFile(snapshot, pgf.File, pkg.Metadata())
   130  	s, err := NewSignature(ctx, snapshot, pkg, sig, comment, qf, mq)
   131  	if err != nil {
   132  		return nil, 0, err
   133  	}
   134  	paramInfo := make([]protocol.ParameterInformation, 0, len(s.params))
   135  	for _, p := range s.params {
   136  		paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p})
   137  	}
   138  	return &protocol.SignatureInformation{
   139  		Label:         name + s.Format(),
   140  		Documentation: stringToSigInfoDocumentation(s.doc, snapshot.Options()),
   141  		Parameters:    paramInfo,
   142  	}, activeParam, nil
   143  }
   144  
   145  func builtinSignature(ctx context.Context, snapshot *cache.Snapshot, callExpr *ast.CallExpr, name string, pos token.Pos) (*protocol.SignatureInformation, int, error) {
   146  	sig, err := NewBuiltinSignature(ctx, snapshot, name)
   147  	if err != nil {
   148  		return nil, 0, err
   149  	}
   150  	paramInfo := make([]protocol.ParameterInformation, 0, len(sig.params))
   151  	for _, p := range sig.params {
   152  		paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p})
   153  	}
   154  	activeParam := activeParameter(callExpr, len(sig.params), sig.variadic, pos)
   155  	return &protocol.SignatureInformation{
   156  		Label:         sig.name + sig.Format(),
   157  		Documentation: stringToSigInfoDocumentation(sig.doc, snapshot.Options()),
   158  		Parameters:    paramInfo,
   159  	}, activeParam, nil
   160  }
   161  
   162  func activeParameter(callExpr *ast.CallExpr, numParams int, variadic bool, pos token.Pos) (activeParam int) {
   163  	if len(callExpr.Args) == 0 {
   164  		return 0
   165  	}
   166  	// First, check if the position is even in the range of the arguments.
   167  	start, end := callExpr.Lparen, callExpr.Rparen
   168  	if !(start <= pos && pos <= end) {
   169  		return 0
   170  	}
   171  	for _, expr := range callExpr.Args {
   172  		if start == token.NoPos {
   173  			start = expr.Pos()
   174  		}
   175  		end = expr.End()
   176  		if start <= pos && pos <= end {
   177  			break
   178  		}
   179  		// Don't advance the active parameter for the last parameter of a variadic function.
   180  		if !variadic || activeParam < numParams-1 {
   181  			activeParam++
   182  		}
   183  		start = expr.Pos() + 1 // to account for commas
   184  	}
   185  	return activeParam
   186  }
   187  
   188  func stringToSigInfoDocumentation(s string, options *settings.Options) *protocol.Or_SignatureInformation_documentation {
   189  	v := s
   190  	k := protocol.PlainText
   191  	if options.PreferredContentFormat == protocol.Markdown {
   192  		v = CommentToMarkdown(s, options)
   193  		// whether or not content is newline terminated may not matter for LSP clients,
   194  		// but our tests expect trailing newlines to be stripped.
   195  		v = strings.TrimSuffix(v, "\n") // TODO(pjw): change the golden files
   196  		k = protocol.Markdown
   197  	}
   198  	return &protocol.Or_SignatureInformation_documentation{
   199  		Value: protocol.MarkupContent{
   200  			Kind:  k,
   201  			Value: v,
   202  		},
   203  	}
   204  }