golang.org/x/tools/gopls@v0.15.3/internal/golang/stub.go (about) 1 // Copyright 2022 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 "bytes" 9 "context" 10 "fmt" 11 "go/format" 12 "go/parser" 13 "go/token" 14 "go/types" 15 "io" 16 pathpkg "path" 17 "strings" 18 19 "golang.org/x/tools/go/analysis" 20 "golang.org/x/tools/go/ast/astutil" 21 "golang.org/x/tools/gopls/internal/analysis/stubmethods" 22 "golang.org/x/tools/gopls/internal/cache" 23 "golang.org/x/tools/gopls/internal/cache/metadata" 24 "golang.org/x/tools/gopls/internal/cache/parsego" 25 "golang.org/x/tools/gopls/internal/util/bug" 26 "golang.org/x/tools/gopls/internal/util/safetoken" 27 "golang.org/x/tools/internal/diff" 28 "golang.org/x/tools/internal/tokeninternal" 29 ) 30 31 // stubMethodsFixer returns a suggested fix to declare the missing 32 // methods of the concrete type that is assigned to an interface type 33 // at the cursor position. 34 func stubMethodsFixer(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) { 35 nodes, _ := astutil.PathEnclosingInterval(pgf.File, start, end) 36 si := stubmethods.GetStubInfo(pkg.FileSet(), pkg.GetTypesInfo(), nodes, start) 37 if si == nil { 38 return nil, nil, fmt.Errorf("nil interface request") 39 } 40 41 // A function-local type cannot be stubbed 42 // since there's nowhere to put the methods. 43 conc := si.Concrete.Obj() 44 if conc.Parent() != conc.Pkg().Scope() { 45 return nil, nil, fmt.Errorf("local type %q cannot be stubbed", conc.Name()) 46 } 47 48 // Parse the file declaring the concrete type. 49 // 50 // Beware: declPGF is not necessarily covered by pkg.FileSet() or si.Fset. 51 declPGF, _, err := parseFull(ctx, snapshot, si.Fset, conc.Pos()) 52 if err != nil { 53 return nil, nil, fmt.Errorf("failed to parse file %q declaring implementation type: %w", declPGF.URI, err) 54 } 55 if declPGF.Fixed() { 56 return nil, nil, fmt.Errorf("file contains parse errors: %s", declPGF.URI) 57 } 58 59 // Find metadata for the concrete type's declaring package 60 // as we'll need its import mapping. 61 declMeta := findFileInDeps(snapshot, pkg.Metadata(), declPGF.URI) 62 if declMeta == nil { 63 return nil, nil, bug.Errorf("can't find metadata for file %s among dependencies of %s", declPGF.URI, pkg) 64 } 65 66 // Record all direct methods of the current object 67 concreteFuncs := make(map[string]struct{}) 68 for i := 0; i < si.Concrete.NumMethods(); i++ { 69 concreteFuncs[si.Concrete.Method(i).Name()] = struct{}{} 70 } 71 72 // Find subset of interface methods that the concrete type lacks. 73 ifaceType := si.Interface.Type().Underlying().(*types.Interface) 74 75 type missingFn struct { 76 fn *types.Func 77 needSubtle string 78 } 79 80 var ( 81 missing []missingFn 82 concreteStruct, isStruct = si.Concrete.Origin().Underlying().(*types.Struct) 83 ) 84 85 for i := 0; i < ifaceType.NumMethods(); i++ { 86 imethod := ifaceType.Method(i) 87 cmethod, index, _ := types.LookupFieldOrMethod(si.Concrete, si.Pointer, imethod.Pkg(), imethod.Name()) 88 if cmethod == nil { 89 missing = append(missing, missingFn{fn: imethod}) 90 continue 91 } 92 93 if _, ok := cmethod.(*types.Var); ok { 94 // len(LookupFieldOrMethod.index) = 1 => conflict, >1 => shadow. 95 return nil, nil, fmt.Errorf("adding method %s.%s would conflict with (or shadow) existing field", 96 conc.Name(), imethod.Name()) 97 } 98 99 if _, exist := concreteFuncs[imethod.Name()]; exist { 100 if !types.Identical(cmethod.Type(), imethod.Type()) { 101 return nil, nil, fmt.Errorf("method %s.%s already exists but has the wrong type: got %s, want %s", 102 conc.Name(), imethod.Name(), cmethod.Type(), imethod.Type()) 103 } 104 continue 105 } 106 107 mf := missingFn{fn: imethod} 108 if isStruct && len(index) > 0 { 109 field := concreteStruct.Field(index[0]) 110 111 fn := field.Name() 112 if _, ok := field.Type().(*types.Pointer); ok { 113 fn = "*" + fn 114 } 115 116 mf.needSubtle = fmt.Sprintf("// Subtle: this method shadows the method (%s).%s of %s.%s.\n", fn, imethod.Name(), si.Concrete.Obj().Name(), field.Name()) 117 } 118 119 missing = append(missing, mf) 120 } 121 if len(missing) == 0 { 122 return nil, nil, fmt.Errorf("no missing methods found") 123 } 124 125 // Build import environment for the declaring file. 126 // (typesutil.FileQualifier works only for complete 127 // import mappings, and requires types.) 128 importEnv := make(map[ImportPath]string) // value is local name 129 for _, imp := range declPGF.File.Imports { 130 importPath := metadata.UnquoteImportPath(imp) 131 var name string 132 if imp.Name != nil { 133 name = imp.Name.Name 134 if name == "_" { 135 continue 136 } else if name == "." { 137 name = "" // see types.Qualifier 138 } 139 } else { 140 // Use the correct name from the metadata of the imported 141 // package---not a guess based on the import path. 142 mp := snapshot.Metadata(declMeta.DepsByImpPath[importPath]) 143 if mp == nil { 144 continue // can't happen? 145 } 146 name = string(mp.Name) 147 } 148 importEnv[importPath] = name // latest alias wins 149 } 150 151 // Create a package name qualifier that uses the 152 // locally appropriate imported package name. 153 // It records any needed new imports. 154 // TODO(adonovan): factor with golang.FormatVarType? 155 // 156 // Prior to CL 469155 this logic preserved any renaming 157 // imports from the file that declares the interface 158 // method--ostensibly the preferred name for imports of 159 // frequently renamed packages such as protobufs. 160 // Now we use the package's declared name. If this turns out 161 // to be a mistake, then use parseHeader(si.iface.Pos()). 162 // 163 type newImport struct{ name, importPath string } 164 var newImports []newImport // for AddNamedImport 165 qual := func(pkg *types.Package) string { 166 // TODO(adonovan): don't ignore vendor prefix. 167 // 168 // Ignore the current package import. 169 if pkg.Path() == conc.Pkg().Path() { 170 return "" 171 } 172 173 importPath := ImportPath(pkg.Path()) 174 name, ok := importEnv[importPath] 175 if !ok { 176 // Insert new import using package's declared name. 177 // 178 // TODO(adonovan): resolve conflict between declared 179 // name and existing file-level (declPGF.File.Imports) 180 // or package-level (si.Concrete.Pkg.Scope) decls by 181 // generating a fresh name. 182 name = pkg.Name() 183 importEnv[importPath] = name 184 new := newImport{importPath: string(importPath)} 185 // For clarity, use a renaming import whenever the 186 // local name does not match the path's last segment. 187 if name != pathpkg.Base(trimVersionSuffix(new.importPath)) { 188 new.name = name 189 } 190 newImports = append(newImports, new) 191 } 192 return name 193 } 194 195 // Format interface name (used only in a comment). 196 iface := si.Interface.Name() 197 if ipkg := si.Interface.Pkg(); ipkg != nil && ipkg != conc.Pkg() { 198 iface = ipkg.Name() + "." + iface 199 } 200 201 // Pointer receiver? 202 var star string 203 if si.Pointer { 204 star = "*" 205 } 206 207 // If there are any that have named receiver, choose the first one. 208 // Otherwise, use lowercase for the first letter of the object. 209 rn := strings.ToLower(si.Concrete.Obj().Name()[0:1]) 210 for i := 0; i < si.Concrete.NumMethods(); i++ { 211 if recv, ok := si.Concrete.Method(i).Type().(*types.Signature); ok && recv.Recv().Name() != "" { 212 rn = recv.Recv().Name() 213 break 214 } 215 } 216 217 // Check for receiver name conflicts 218 checkRecvName := func(tuple *types.Tuple) bool { 219 for i := 0; i < tuple.Len(); i++ { 220 if rn == tuple.At(i).Name() { 221 return true 222 } 223 } 224 return false 225 } 226 227 // Format the new methods. 228 var newMethods bytes.Buffer 229 230 for index := range missing { 231 mrn := rn + " " 232 if sig, ok := missing[index].fn.Type().(*types.Signature); ok { 233 if checkRecvName(sig.Params()) || checkRecvName(sig.Results()) { 234 mrn = "" 235 } 236 } 237 238 fmt.Fprintf(&newMethods, `// %s implements %s. 239 %sfunc (%s%s%s%s) %s%s { 240 panic("unimplemented") 241 } 242 `, 243 missing[index].fn.Name(), 244 iface, 245 missing[index].needSubtle, 246 mrn, 247 star, 248 si.Concrete.Obj().Name(), 249 FormatTypeParams(si.Concrete.TypeParams()), 250 missing[index].fn.Name(), 251 strings.TrimPrefix(types.TypeString(missing[index].fn.Type(), qual), "func")) 252 } 253 254 // Compute insertion point for new methods: 255 // after the top-level declaration enclosing the (package-level) type. 256 insertOffset, err := safetoken.Offset(declPGF.Tok, declPGF.File.End()) 257 if err != nil { 258 return nil, nil, bug.Errorf("internal error: end position outside file bounds: %v", err) 259 } 260 concOffset, err := safetoken.Offset(si.Fset.File(conc.Pos()), conc.Pos()) 261 if err != nil { 262 return nil, nil, bug.Errorf("internal error: finding type decl offset: %v", err) 263 } 264 for _, decl := range declPGF.File.Decls { 265 declEndOffset, err := safetoken.Offset(declPGF.Tok, decl.End()) 266 if err != nil { 267 return nil, nil, bug.Errorf("internal error: finding decl offset: %v", err) 268 } 269 if declEndOffset > concOffset { 270 insertOffset = declEndOffset 271 break 272 } 273 } 274 275 // Splice the new methods into the file content. 276 var buf bytes.Buffer 277 input := declPGF.Mapper.Content // unfixed content of file 278 buf.Write(input[:insertOffset]) 279 buf.WriteByte('\n') 280 io.Copy(&buf, &newMethods) 281 buf.Write(input[insertOffset:]) 282 283 // Re-parse the file. 284 fset := token.NewFileSet() 285 newF, err := parser.ParseFile(fset, declPGF.URI.Path(), buf.Bytes(), parser.ParseComments) 286 if err != nil { 287 return nil, nil, fmt.Errorf("could not reparse file: %w", err) 288 } 289 290 // Splice the new imports into the syntax tree. 291 for _, imp := range newImports { 292 astutil.AddNamedImport(fset, newF, imp.name, imp.importPath) 293 } 294 295 // Pretty-print. 296 var output bytes.Buffer 297 if err := format.Node(&output, fset, newF); err != nil { 298 return nil, nil, fmt.Errorf("format.Node: %w", err) 299 } 300 301 // Report the diff. 302 diffs := diff.Bytes(input, output.Bytes()) 303 return tokeninternal.FileSetFor(declPGF.Tok), // edits use declPGF.Tok 304 &analysis.SuggestedFix{TextEdits: diffToTextEdits(declPGF.Tok, diffs)}, 305 nil 306 } 307 308 // diffToTextEdits converts diff (offset-based) edits to analysis (token.Pos) form. 309 func diffToTextEdits(tok *token.File, diffs []diff.Edit) []analysis.TextEdit { 310 edits := make([]analysis.TextEdit, 0, len(diffs)) 311 for _, edit := range diffs { 312 edits = append(edits, analysis.TextEdit{ 313 Pos: tok.Pos(edit.Start), 314 End: tok.Pos(edit.End), 315 NewText: []byte(edit.New), 316 }) 317 } 318 return edits 319 } 320 321 // trimVersionSuffix removes a trailing "/v2" (etc) suffix from a module path. 322 // 323 // This is only a heuristic as to the package's declared name, and 324 // should only be used for stylistic decisions, such as whether it 325 // would be clearer to use an explicit local name in the import 326 // because the declared name differs from the result of this function. 327 // When the name matters for correctness, look up the imported 328 // package's Metadata.Name. 329 func trimVersionSuffix(path string) string { 330 dir, base := pathpkg.Split(path) 331 if len(base) > 1 && base[0] == 'v' && strings.Trim(base[1:], "0123456789") == "" { 332 return dir // sans "/v2" 333 } 334 return path 335 }