github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/source/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 source 6 7 import ( 8 "context" 9 "go/ast" 10 "go/token" 11 "go/types" 12 13 "github.com/jhump/golang-x-tools/go/ast/astutil" 14 "github.com/jhump/golang-x-tools/internal/event" 15 "github.com/jhump/golang-x-tools/internal/lsp/protocol" 16 errors "golang.org/x/xerrors" 17 ) 18 19 func SignatureHelp(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) (*protocol.SignatureInformation, int, error) { 20 ctx, done := event.Start(ctx, "source.SignatureHelp") 21 defer done() 22 23 pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage) 24 if err != nil { 25 return nil, 0, errors.Errorf("getting file for SignatureHelp: %w", err) 26 } 27 spn, err := pgf.Mapper.PointSpan(pos) 28 if err != nil { 29 return nil, 0, err 30 } 31 rng, err := spn.Range(pgf.Mapper.Converter) 32 if err != nil { 33 return nil, 0, err 34 } 35 // Find a call expression surrounding the query position. 36 var callExpr *ast.CallExpr 37 path, _ := astutil.PathEnclosingInterval(pgf.File, rng.Start, rng.Start) 38 if path == nil { 39 return nil, 0, errors.Errorf("cannot find node enclosing position") 40 } 41 FindCall: 42 for _, node := range path { 43 switch node := node.(type) { 44 case *ast.CallExpr: 45 if rng.Start >= node.Lparen && rng.Start <= node.Rparen { 46 callExpr = node 47 break FindCall 48 } 49 case *ast.FuncLit, *ast.FuncType: 50 // The user is within an anonymous function, 51 // which may be the parameter to the *ast.CallExpr. 52 // Don't show signature help in this case. 53 return nil, 0, errors.Errorf("no signature help within a function declaration") 54 case *ast.BasicLit: 55 if node.Kind == token.STRING { 56 return nil, 0, errors.Errorf("no signature help within a string literal") 57 } 58 } 59 60 } 61 if callExpr == nil || callExpr.Fun == nil { 62 return nil, 0, errors.Errorf("cannot find an enclosing function") 63 } 64 65 qf := Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()) 66 67 // Get the object representing the function, if available. 68 // There is no object in certain cases such as calling a function returned by 69 // a function (e.g. "foo()()"). 70 var obj types.Object 71 switch t := callExpr.Fun.(type) { 72 case *ast.Ident: 73 obj = pkg.GetTypesInfo().ObjectOf(t) 74 case *ast.SelectorExpr: 75 obj = pkg.GetTypesInfo().ObjectOf(t.Sel) 76 } 77 78 // Handle builtin functions separately. 79 if obj, ok := obj.(*types.Builtin); ok { 80 return builtinSignature(ctx, snapshot, callExpr, obj.Name(), rng.Start) 81 } 82 83 // Get the type information for the function being called. 84 sigType := pkg.GetTypesInfo().TypeOf(callExpr.Fun) 85 if sigType == nil { 86 return nil, 0, errors.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun) 87 } 88 89 sig, _ := sigType.Underlying().(*types.Signature) 90 if sig == nil { 91 return nil, 0, errors.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun) 92 } 93 94 activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), rng.Start) 95 96 var ( 97 name string 98 comment *ast.CommentGroup 99 ) 100 if obj != nil { 101 declPkg, err := FindPackageFromPos(ctx, snapshot, obj.Pos()) 102 if err != nil { 103 return nil, 0, err 104 } 105 node, err := snapshot.PosToDecl(ctx, declPkg, obj.Pos()) 106 if err != nil { 107 return nil, 0, err 108 } 109 rng, err := objToMappedRange(snapshot, pkg, obj) 110 if err != nil { 111 return nil, 0, err 112 } 113 decl := Declaration{ 114 obj: obj, 115 node: node, 116 } 117 decl.MappedRange = append(decl.MappedRange, rng) 118 d, err := HoverInfo(ctx, snapshot, pkg, decl.obj, decl.node, nil) 119 if err != nil { 120 return nil, 0, err 121 } 122 name = obj.Name() 123 comment = d.comment 124 } else { 125 name = "func" 126 } 127 s := NewSignature(ctx, snapshot, pkg, sig, comment, qf) 128 paramInfo := make([]protocol.ParameterInformation, 0, len(s.params)) 129 for _, p := range s.params { 130 paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p}) 131 } 132 return &protocol.SignatureInformation{ 133 Label: name + s.Format(), 134 Documentation: s.doc, 135 Parameters: paramInfo, 136 }, activeParam, nil 137 } 138 139 func builtinSignature(ctx context.Context, snapshot Snapshot, callExpr *ast.CallExpr, name string, pos token.Pos) (*protocol.SignatureInformation, int, error) { 140 sig, err := NewBuiltinSignature(ctx, snapshot, name) 141 if err != nil { 142 return nil, 0, err 143 } 144 paramInfo := make([]protocol.ParameterInformation, 0, len(sig.params)) 145 for _, p := range sig.params { 146 paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p}) 147 } 148 activeParam := activeParameter(callExpr, len(sig.params), sig.variadic, pos) 149 return &protocol.SignatureInformation{ 150 Label: sig.name + sig.Format(), 151 Documentation: sig.doc, 152 Parameters: paramInfo, 153 }, activeParam, nil 154 155 } 156 157 func activeParameter(callExpr *ast.CallExpr, numParams int, variadic bool, pos token.Pos) (activeParam int) { 158 if len(callExpr.Args) == 0 { 159 return 0 160 } 161 // First, check if the position is even in the range of the arguments. 162 start, end := callExpr.Lparen, callExpr.Rparen 163 if !(start <= pos && pos <= end) { 164 return 0 165 } 166 for _, expr := range callExpr.Args { 167 if start == token.NoPos { 168 start = expr.Pos() 169 } 170 end = expr.End() 171 if start <= pos && pos <= end { 172 break 173 } 174 // Don't advance the active parameter for the last parameter of a variadic function. 175 if !variadic || activeParam < numParams-1 { 176 activeParam++ 177 } 178 start = expr.Pos() + 1 // to account for commas 179 } 180 return activeParam 181 }