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 }