├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── go.mod ├── go.sum ├── impl.go ├── impl_test.go ├── implemented.go └── testdata ├── free_floating.go └── interfaces.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: "ubuntu-latest" 8 | steps: 9 | - uses: actions/checkout@master 10 | - uses: actions/setup-go@v4 11 | with: 12 | go-version-file: "go.mod" 13 | cache: true 14 | - name: run go tests 15 | run: | 16 | go test -v ./... 17 | go test -v -race ./... 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | impl 2 | c.out 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Josh Bleecher Snyder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `impl` generates method stubs for implementing an interface. 2 | 3 | ```bash 4 | go install github.com/josharian/impl@latest 5 | ``` 6 | 7 | Sample usage: 8 | 9 | ```bash 10 | $ impl 'f *File' io.ReadWriteCloser 11 | func (f *File) Read(p []byte) (n int, err error) { 12 | panic("not implemented") 13 | } 14 | 15 | func (f *File) Write(p []byte) (n int, err error) { 16 | panic("not implemented") 17 | } 18 | 19 | func (f *File) Close() error { 20 | panic("not implemented") 21 | } 22 | 23 | # You can also provide a full name by specifying the package path. 24 | # This helps in cases where the interface can't be guessed 25 | # just from the package name and interface name. 26 | $ impl 's *Source' golang.org/x/oauth2.TokenSource 27 | func (s *Source) Token() (*oauth2.Token, error) { 28 | panic("not implemented") 29 | } 30 | ``` 31 | 32 | You can use `impl` from Vim with [vim-go](https://github.com/fatih/vim-go) or 33 | [vim-go-impl](https://github.com/rhysd/vim-go-impl) 34 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/josharian/impl 2 | 3 | go 1.18 4 | 5 | require golang.org/x/tools v0.17.0 6 | 7 | require ( 8 | golang.org/x/mod v0.14.0 // indirect 9 | golang.org/x/sys v0.16.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= 2 | golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 3 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 4 | golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 5 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= 6 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 7 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 8 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 9 | golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= 10 | golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= 11 | golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= 12 | golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= 13 | -------------------------------------------------------------------------------- /impl.go: -------------------------------------------------------------------------------- 1 | // impl generates method stubs for implementing an interface. 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "flag" 7 | "fmt" 8 | "go/ast" 9 | "go/build" 10 | "go/format" 11 | "go/parser" 12 | "go/printer" 13 | "go/token" 14 | "os" 15 | "path/filepath" 16 | "strconv" 17 | "strings" 18 | "text/template" 19 | 20 | "golang.org/x/tools/imports" 21 | ) 22 | 23 | var ( 24 | flagSrcDir = flag.String("dir", "", "package source directory, useful for vendored code") 25 | flagComments = flag.Bool("comments", true, "include interface comments in the generated stubs") 26 | flagRecvPkg = flag.String("recvpkg", "", "package name of the receiver") 27 | ) 28 | 29 | // Type is a parsed type reference. 30 | type Type struct { 31 | // Name is the type's name. For example, in "foo[Bar, Baz]", the name 32 | // is "foo". 33 | Name string 34 | 35 | // Params are the type's type params. For example, in "foo[Bar, Baz]", 36 | // the Params are []string{"Bar", "Baz"}. 37 | // 38 | // Params never list the type of the "name type" construction of type 39 | // params used when defining a generic type. They will always be just 40 | // the filling type, as seen when using a generic type. 41 | // 42 | // Params will always be the type parameters only for the top-level 43 | // type; if the params themselves have type parameters, they will 44 | // remain joined to the type name. So "foo[Bar, Baz[Quux]]" will be 45 | // returned as {ID: "foo", Params: []string{"Bar", "Baz[Quux]"}} 46 | Params []string 47 | } 48 | 49 | // String constructs a reference to the Type. For example: 50 | // Type{Name: "Foo", Params{"Bar", "Baz[[]Quux]"}} 51 | // would yield 52 | // Foo[Bar, Baz[[]Quux]] 53 | func (t Type) String() string { 54 | if len(t.Params) < 1 { 55 | return t.Name 56 | } 57 | return t.Name + "[" + strings.Join(t.Params, ", ") + "]" 58 | } 59 | 60 | // parseType parses an interface reference into a Type, allowing us to 61 | // distinguish between the interface's name and its type parameters. 62 | func parseType(in string) (Type, error) { 63 | expr, err := parser.ParseExpr(in) 64 | if err != nil { 65 | return Type{}, err 66 | } 67 | return typeFromAST(expr) 68 | } 69 | 70 | // findInterface returns the import path and type of an interface. 71 | // For example, given "http.ResponseWriter", findInterface returns 72 | // "net/http", Type{Name: "ResponseWriter"}. 73 | // If a fully qualified interface is given, such as "net/http.ResponseWriter", 74 | // it simply parses the input. 75 | // If an unqualified interface such as "UserDefinedInterface" is given, then 76 | // the interface definition is presumed to be in the package within srcDir and 77 | // findInterface returns "", Type{Name: "UserDefinedInterface"}. 78 | // 79 | // Generic types will have their type params set in the Params property of 80 | // the Type. Input should always reference generic types with their parameters 81 | // specified: GenericType[string, bool], not GenericType[A any, B comparable]. 82 | func findInterface(input string, srcDir string) (path string, iface Type, err error) { 83 | if len(strings.Fields(input)) != 1 && !strings.Contains(input, "[") { 84 | return "", Type{}, fmt.Errorf("couldn't parse interface: %s", input) 85 | } 86 | 87 | srcPath := filepath.Join(srcDir, "__go_impl__.go") 88 | 89 | if slash := strings.LastIndex(input, "/"); slash > -1 { 90 | // package path provided 91 | dot := strings.LastIndex(input, ".") 92 | // make sure iface does not end with "/" (e.g. reject net/http/) 93 | if slash+1 == len(input) { 94 | return "", Type{}, fmt.Errorf("interface name cannot end with a '/' character: %s", input) 95 | } 96 | // make sure iface does not end with "." (e.g. reject net/http.) 97 | if dot+1 == len(input) { 98 | return "", Type{}, fmt.Errorf("interface name cannot end with a '.' character: %s", input) 99 | } 100 | // make sure iface has at least one "." after "/" (e.g. reject net/http/httputil) 101 | if strings.Count(input[slash:], ".") == 0 { 102 | return "", Type{}, fmt.Errorf("invalid interface name: %s", input) 103 | } 104 | path = input[:dot] 105 | id := input[dot+1:] 106 | iface, err = parseType(id) 107 | if err != nil { 108 | return "", Type{}, err 109 | } 110 | return path, iface, nil 111 | } 112 | 113 | src := []byte("package hack\n" + "var i " + input) 114 | // If we couldn't determine the import path, goimports will 115 | // auto fix the import path. 116 | imp, err := imports.Process(srcPath, src, nil) 117 | if err != nil { 118 | return "", Type{}, fmt.Errorf("couldn't parse interface: %s", input) 119 | } 120 | 121 | // imp should now contain an appropriate import. 122 | // Parse out the import and the identifier. 123 | fset := token.NewFileSet() 124 | f, err := parser.ParseFile(fset, srcPath, imp, 0) 125 | if err != nil { 126 | panic(err) 127 | } 128 | 129 | qualified := strings.Contains(input, ".") 130 | 131 | if len(f.Imports) == 0 && qualified { 132 | return "", Type{}, fmt.Errorf("unrecognized interface: %s", input) 133 | } 134 | 135 | if !qualified { 136 | // If !qualified, the code looks like: 137 | // 138 | // package hack 139 | // 140 | // var i Reader 141 | decl := f.Decls[0].(*ast.GenDecl) // var i Reader 142 | spec := decl.Specs[0].(*ast.ValueSpec) // i Reader 143 | iface, err = typeFromAST(spec.Type) 144 | return path, iface, err 145 | } 146 | 147 | // If qualified, the code looks like: 148 | // 149 | // package hack 150 | // 151 | // import ( 152 | // "io" 153 | // ) 154 | // 155 | // var i io.Reader 156 | raw := f.Imports[0].Path.Value // "io" 157 | path, err = strconv.Unquote(raw) // io 158 | if err != nil { 159 | panic(err) 160 | } 161 | decl := f.Decls[1].(*ast.GenDecl) // var i io.Reader 162 | spec := decl.Specs[0].(*ast.ValueSpec) // i io.Reader 163 | iface, err = typeFromAST(spec.Type) 164 | if err != nil { 165 | return path, iface, fmt.Errorf("error parsing type from AST: %w", err) 166 | } 167 | // trim off the package which got smooshed on when resolving the type 168 | _, iface.Name, _ = strings.Cut(iface.Name, ".") 169 | return path, iface, err 170 | } 171 | 172 | func typeFromAST(in ast.Expr) (Type, error) { 173 | // Extract type name and params from generic types. 174 | var typeName ast.Expr 175 | var typeParams []ast.Expr 176 | switch in := in.(type) { 177 | case *ast.IndexExpr: 178 | // a generic type with one type parameter (Reader[Foo]) shows up as an IndexExpr 179 | typeName = in.X 180 | typeParams = []ast.Expr{in.Index} 181 | case *ast.IndexListExpr: 182 | // a generic type with multiple type parameters shows up as an IndexListExpr 183 | typeName = in.X 184 | typeParams = in.Indices 185 | } 186 | if typeParams != nil { 187 | id, err := typeFromAST(typeName) 188 | if err != nil { 189 | return Type{}, err 190 | } 191 | if len(id.Params) > 0 { 192 | return Type{}, fmt.Errorf("unexpected type parameters: %v", in) 193 | } 194 | res := Type{Name: id.Name} 195 | for _, typeParam := range typeParams { 196 | param, err := typeFromAST(typeParam) 197 | if err != nil { 198 | return Type{}, err 199 | } 200 | res.Params = append(res.Params, param.String()) 201 | } 202 | return res, nil 203 | } 204 | // Non-generic type. 205 | buf := new(strings.Builder) 206 | err := format.Node(buf, token.NewFileSet(), in) 207 | if err != nil { 208 | return Type{}, err 209 | } 210 | return Type{Name: buf.String()}, nil 211 | } 212 | 213 | // Pkg is a parsed build.Package. 214 | type Pkg struct { 215 | *build.Package 216 | *token.FileSet 217 | // recvPkg is the package name of the function receiver 218 | recvPkg string 219 | } 220 | 221 | // Spec is ast.TypeSpec with the associated comment map. 222 | type Spec struct { 223 | *ast.TypeSpec 224 | ast.CommentMap 225 | TypeParams map[string]string 226 | } 227 | 228 | // typeSpec locates the *ast.TypeSpec for type id in the import path. 229 | func typeSpec(path string, typ Type, srcDir string) (Pkg, Spec, error) { 230 | var pkg *build.Package 231 | var err error 232 | 233 | if path == "" { 234 | pkg, err = build.ImportDir(srcDir, 0) 235 | if err != nil { 236 | return Pkg{}, Spec{}, fmt.Errorf("couldn't find package in %s: %v", srcDir, err) 237 | } 238 | } else { 239 | pkg, err = build.Import(path, srcDir, 0) 240 | if err != nil { 241 | return Pkg{}, Spec{}, fmt.Errorf("couldn't find package %s: %v", path, err) 242 | } 243 | } 244 | 245 | fset := token.NewFileSet() // share one fset across the whole package 246 | var files []string 247 | files = append(files, pkg.GoFiles...) 248 | files = append(files, pkg.CgoFiles...) 249 | for _, file := range files { 250 | f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, file), nil, parser.ParseComments) 251 | if err != nil { 252 | continue 253 | } 254 | 255 | for _, decl := range f.Decls { 256 | decl, ok := decl.(*ast.GenDecl) 257 | if !ok || decl.Tok != token.TYPE { 258 | continue 259 | } 260 | for _, spec := range decl.Specs { 261 | spec := spec.(*ast.TypeSpec) 262 | if spec.Name.Name != typ.Name { 263 | continue 264 | } 265 | typeParams, ok := matchTypeParams(spec, typ.Params) 266 | if !ok { 267 | continue 268 | } 269 | p := Pkg{Package: pkg, FileSet: fset} 270 | s := Spec{TypeSpec: spec, TypeParams: typeParams} 271 | return p, s, nil 272 | } 273 | } 274 | } 275 | return Pkg{}, Spec{}, fmt.Errorf("type %s not found in %s", typ.Name, path) 276 | } 277 | 278 | // matchTypeParams returns a map of type parameters from a parsed interface 279 | // definition and the types that fill them from the user's specified type 280 | // info. If the passed params can't be used to fill the type parameters on the 281 | // passed type, a nil map and false are returned. No type checking is done, 282 | // only that there are sufficient types to match. 283 | func matchTypeParams(spec *ast.TypeSpec, params []string) (map[string]string, bool) { 284 | if spec.TypeParams == nil { 285 | return nil, true 286 | } 287 | res := make(map[string]string, len(params)) 288 | var specParamNames []string 289 | for _, typeParam := range spec.TypeParams.List { 290 | for _, name := range typeParam.Names { 291 | if name == nil { 292 | continue 293 | } 294 | specParamNames = append(specParamNames, name.Name) 295 | } 296 | } 297 | if len(specParamNames) != len(params) { 298 | return nil, false 299 | } 300 | for pos, specParamName := range specParamNames { 301 | res[specParamName] = params[pos] 302 | } 303 | return res, true 304 | } 305 | 306 | // gofmt pretty-prints e. 307 | func (p Pkg) gofmt(e ast.Expr) string { 308 | var buf bytes.Buffer 309 | printer.Fprint(&buf, p.FileSet, e) 310 | return buf.String() 311 | } 312 | 313 | // fullType returns the fully qualified type of e. 314 | // Examples, assuming package net/http: 315 | // 316 | // fullType(int) => "int" 317 | // fullType(Handler) => "http.Handler" 318 | // fullType(io.Reader) => "io.Reader" 319 | // fullType(*Request) => "*http.Request" 320 | func (p Pkg) fullType(e ast.Expr) string { 321 | ast.Inspect(e, func(n ast.Node) bool { 322 | switch n := n.(type) { 323 | case *ast.Ident: 324 | // Using typeSpec instead of IsExported here would be 325 | // more accurate, but it'd be crazy expensive, and if 326 | // the type isn't exported, there's no point trying 327 | // to implement it anyway. 328 | if n.IsExported() && p.recvPkg != p.Package.Name { 329 | n.Name = p.Package.Name + "." + n.Name 330 | } 331 | case *ast.SelectorExpr: 332 | return false 333 | } 334 | return true 335 | }) 336 | return p.gofmt(e) 337 | } 338 | 339 | func (p Pkg) params(field *ast.Field, typeParams map[string]string) []Param { 340 | var params []Param 341 | var typ string 342 | switch expr := field.Type.(type) { 343 | case *ast.Ident: 344 | if genType, ok := typeParams[expr.Name]; ok { 345 | typ = genType 346 | } else { 347 | typ = p.fullType(field.Type) 348 | } 349 | default: 350 | typ = p.fullType(field.Type) 351 | } 352 | for _, name := range field.Names { 353 | params = append(params, Param{Name: name.Name, Type: typ}) 354 | } 355 | // Handle anonymous params 356 | if len(params) == 0 { 357 | params = []Param{{Type: typ}} 358 | } 359 | return params 360 | } 361 | 362 | // Method represents a method signature. 363 | type Method struct { 364 | Recv string 365 | Func 366 | } 367 | 368 | // Func represents a function signature. 369 | type Func struct { 370 | Name string 371 | Params []Param 372 | Res []Param 373 | Comments string 374 | } 375 | 376 | // Param represents a parameter in a function or method signature. 377 | type Param struct { 378 | Name string 379 | Type string 380 | } 381 | 382 | // EmitComments specifies whether comments from the interface should be preserved in the implementation. 383 | type EmitComments bool 384 | 385 | const ( 386 | WithComments EmitComments = true 387 | WithoutComments EmitComments = false 388 | ) 389 | 390 | func (p Pkg) funcsig(f *ast.Field, typeParams map[string]string, cmap ast.CommentMap, comments EmitComments) Func { 391 | fn := Func{Name: f.Names[0].Name} 392 | typ := f.Type.(*ast.FuncType) 393 | if typ.Params != nil { 394 | for _, field := range typ.Params.List { 395 | for _, param := range p.params(field, typeParams) { 396 | // only for method parameters: 397 | // assign a blank identifier "_" to an anonymous parameter 398 | if param.Name == "" { 399 | param.Name = "_" 400 | } 401 | fn.Params = append(fn.Params, param) 402 | } 403 | } 404 | } 405 | if typ.Results != nil { 406 | for _, field := range typ.Results.List { 407 | fn.Res = append(fn.Res, p.params(field, typeParams)...) 408 | } 409 | } 410 | if comments == WithComments && f.Doc != nil { 411 | fn.Comments = flattenDocComment(f) 412 | } 413 | return fn 414 | } 415 | 416 | // The error interface is built-in. 417 | var errorInterface = []Func{{ 418 | Name: "Error", 419 | Res: []Param{{Type: "string"}}, 420 | }} 421 | 422 | // funcs returns the set of methods required to implement iface. 423 | // It is called funcs rather than methods because the 424 | // function descriptions are functions; there is no receiver. 425 | func funcs(iface, srcDir, recvPkg string, comments EmitComments) ([]Func, error) { 426 | // Special case for the built-in error interface. 427 | if iface == "error" { 428 | return errorInterface, nil 429 | } 430 | 431 | // Locate the interface. 432 | path, typ, err := findInterface(iface, srcDir) 433 | if err != nil { 434 | return nil, err 435 | } 436 | 437 | // Parse the package and find the interface declaration. 438 | p, spec, err := typeSpec(path, typ, srcDir) 439 | if err != nil { 440 | return nil, fmt.Errorf("interface %s not found: %s", iface, err) 441 | } 442 | p.recvPkg = recvPkg 443 | 444 | idecl, ok := spec.Type.(*ast.InterfaceType) 445 | if !ok { 446 | return nil, fmt.Errorf("not an interface: %s", iface) 447 | } 448 | 449 | if idecl.Methods == nil { 450 | return nil, fmt.Errorf("empty interface: %s", iface) 451 | } 452 | 453 | var fns []Func 454 | for _, fndecl := range idecl.Methods.List { 455 | if len(fndecl.Names) == 0 { 456 | // Embedded interface: recurse 457 | embedded, err := funcs(p.fullType(fndecl.Type), srcDir, recvPkg, comments) 458 | if err != nil { 459 | return nil, err 460 | } 461 | fns = append(fns, embedded...) 462 | continue 463 | } 464 | 465 | fn := p.funcsig(fndecl, spec.TypeParams, spec.CommentMap.Filter(fndecl), comments) 466 | fns = append(fns, fn) 467 | } 468 | return fns, nil 469 | } 470 | 471 | const stub = "{{if .Comments}}{{.Comments}}{{end}}" + 472 | "func ({{.Recv}}) {{.Name}}" + 473 | "({{range .Params}}{{.Name}} {{.Type}}, {{end}})" + 474 | "({{range .Res}}{{.Name}} {{.Type}}, {{end}})" + 475 | "{\n" + "panic(\"not implemented\") // TODO: Implement" + "\n}\n\n" 476 | 477 | var tmpl = template.Must(template.New("test").Parse(stub)) 478 | 479 | // genStubs prints nicely formatted method stubs 480 | // for fns using receiver expression recv. 481 | // If recv is not a valid receiver expression, 482 | // genStubs will panic. 483 | // genStubs won't generate stubs for 484 | // already implemented methods of receiver. 485 | func genStubs(recv string, fns []Func, implemented map[string]bool) []byte { 486 | var recvName string 487 | if recvs := strings.Fields(recv); len(recvs) > 1 { 488 | recvName = recvs[0] 489 | } 490 | 491 | // (r *recv) F(r string) {} => (r *recv) F(_ string) 492 | fixParams := func(params []Param) { 493 | for i, p := range params { 494 | if p.Name == recvName { 495 | params[i].Name = "_" 496 | } 497 | } 498 | } 499 | 500 | buf := new(bytes.Buffer) 501 | for _, fn := range fns { 502 | if implemented[fn.Name] { 503 | continue 504 | } 505 | 506 | fixParams(fn.Params) 507 | fixParams(fn.Res) 508 | meth := Method{Recv: recv, Func: fn} 509 | tmpl.Execute(buf, meth) 510 | } 511 | 512 | pretty, err := format.Source(buf.Bytes()) 513 | if err != nil { 514 | panic(err) 515 | } 516 | return pretty 517 | } 518 | 519 | // validReceiver reports whether recv is a valid receiver expression. 520 | func validReceiver(recv string) bool { 521 | if recv == "" { 522 | // The parse will parse empty receivers, but we don't want to accept them, 523 | // since it won't generate a usable code snippet. 524 | return false 525 | } 526 | fset := token.NewFileSet() 527 | _, err := parser.ParseFile(fset, "", "package hack\nfunc ("+recv+") Foo()", 0) 528 | return err == nil 529 | } 530 | 531 | // flattenDocComment flattens the field doc comments to a string 532 | func flattenDocComment(f *ast.Field) string { 533 | var result strings.Builder 534 | for _, c := range f.Doc.List { 535 | result.WriteString(c.Text) 536 | // add an end-of-line character if this is '//'-style comment 537 | if c.Text[1] == '/' { 538 | result.WriteString("\n") 539 | } 540 | } 541 | 542 | // for '/*'-style comments, make sure to append EOL character to the comment 543 | // block 544 | if s := result.String(); !strings.HasSuffix(s, "\n") { 545 | result.WriteString("\n") 546 | } 547 | 548 | return result.String() 549 | } 550 | 551 | func main() { 552 | flag.Usage = func() { 553 | fmt.Fprint(os.Stderr, ` 554 | impl generates method stubs for recv to implement iface. 555 | 556 | impl [-dir directory] 557 | 558 | `[1:]) 559 | flag.PrintDefaults() 560 | fmt.Fprint(os.Stderr, ` 561 | 562 | Examples: 563 | 564 | impl 'f *File' io.Reader 565 | impl Murmur hash.Hash 566 | impl -dir $GOPATH/src/github.com/josharian/impl Murmur hash.Hash 567 | 568 | Don't forget the single quotes around the receiver type 569 | to prevent shell globbing. 570 | `[1:]) 571 | os.Exit(2) 572 | } 573 | flag.Parse() 574 | 575 | if len(flag.Args()) < 2 { 576 | flag.Usage() 577 | } 578 | 579 | recv, iface := flag.Arg(0), flag.Arg(1) 580 | if !validReceiver(recv) { 581 | fatal(fmt.Sprintf("invalid receiver: %q", recv)) 582 | } 583 | 584 | if *flagSrcDir == "" { 585 | if dir, err := os.Getwd(); err == nil { 586 | *flagSrcDir = dir 587 | } 588 | } 589 | 590 | recvPkg := *flagRecvPkg 591 | if recvPkg == "" { 592 | // " s *Struct " , receiver: Struct 593 | recvs := strings.Fields(recv) 594 | receiver := recvs[len(recvs)-1] // note that this correctly handles "s *Struct" and "*Struct" 595 | receiver = strings.TrimPrefix(receiver, "*") 596 | pkg, _, err := typeSpec("", Type{Name: receiver}, *flagSrcDir) 597 | if err == nil { 598 | recvPkg = pkg.Package.Name 599 | } 600 | } 601 | 602 | fns, err := funcs(iface, *flagSrcDir, recvPkg, EmitComments(*flagComments)) 603 | if err != nil { 604 | fatal(err) 605 | } 606 | 607 | // Get list of already implemented funcs 608 | implemented, err := implementedFuncs(fns, recv, *flagSrcDir) 609 | if err != nil { 610 | fatal(err) 611 | } 612 | 613 | src := genStubs(recv, fns, implemented) 614 | fmt.Print(string(src)) 615 | } 616 | 617 | func fatal(msg interface{}) { 618 | fmt.Fprintln(os.Stderr, msg) 619 | os.Exit(1) 620 | } 621 | -------------------------------------------------------------------------------- /impl_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/josharian/impl/testdata" 9 | ) 10 | 11 | type errBool bool 12 | 13 | func (b errBool) String() string { 14 | if b { 15 | return "an error" 16 | } 17 | return "no error" 18 | } 19 | 20 | func TestFindInterface(t *testing.T) { 21 | t.Parallel() 22 | cases := []struct { 23 | input string 24 | path string 25 | typ Type 26 | wantErr bool 27 | }{ 28 | {input: "net.Conn", path: "net", typ: Type{Name: "Conn"}}, 29 | {input: "http.ResponseWriter", path: "net/http", typ: Type{Name: "ResponseWriter"}}, 30 | {input: "net.Tennis", wantErr: true}, 31 | {input: "a + b", wantErr: true}, 32 | {input: "t[T,U]", path: "", typ: Type{Name: "t", Params: []string{"T", "U"}}}, 33 | {input: "a/b/c/", wantErr: true}, 34 | {input: "a/b/c/pkg", wantErr: true}, 35 | {input: "a/b/c/pkg.", wantErr: true}, 36 | {input: "a/b/c/pkg.Typ", path: "a/b/c/pkg", typ: Type{Name: "Typ"}}, 37 | {input: "gopkg.in/yaml.v2.Unmarshaler", path: "gopkg.in/yaml.v2", typ: Type{Name: "Unmarshaler"}}, 38 | {input: "github.com/josharian/impl/testdata.GenericInterface1[string]", path: "github.com/josharian/impl/testdata", typ: Type{Name: "GenericInterface1", Params: []string{"string"}}}, 39 | {input: "github.com/josharian/impl/testdata.GenericInterface1[*string]", path: "github.com/josharian/impl/testdata", typ: Type{Name: "GenericInterface1", Params: []string{"*string"}}}, 40 | } 41 | 42 | for _, tt := range cases { 43 | tt := tt 44 | t.Run(tt.input, func(t *testing.T) { 45 | t.Parallel() 46 | path, typ, err := findInterface(tt.input, ".") 47 | gotErr := err != nil 48 | if tt.wantErr != gotErr { 49 | t.Fatalf("findInterface(%q).err=%v want %s", tt.input, err, errBool(tt.wantErr)) 50 | } 51 | if tt.path != path { 52 | t.Errorf("findInterface(%q).path=%q want %q", tt.input, path, tt.path) 53 | } 54 | if tt.typ.Name != typ.Name { 55 | t.Errorf("findInterface(%q).id=%q want %q", tt.input, typ.Name, tt.typ.Name) 56 | } 57 | if len(tt.typ.Params) != len(typ.Params) { 58 | t.Errorf("findInterface(%q).len(typeParams)=%d want %d", tt.input, len(typ.Params), len(tt.typ.Params)) 59 | } 60 | for pos, v := range tt.typ.Params { 61 | if v != typ.Params[pos] { 62 | t.Errorf("findInterface(%q).typeParams[%d]=%q, want %q", tt.input, pos, typ.Params[pos], v) 63 | } 64 | } 65 | }) 66 | } 67 | } 68 | 69 | func TestTypeSpec(t *testing.T) { 70 | // For now, just test whether we can find the interface. 71 | cases := []struct { 72 | path string 73 | typ Type 74 | wantErr bool 75 | }{ 76 | {path: "net", typ: Type{Name: "Conn"}}, 77 | {path: "net", typ: Type{Name: "Con"}, wantErr: true}, 78 | } 79 | 80 | for _, tt := range cases { 81 | p, spec, err := typeSpec(tt.path, tt.typ, "") 82 | gotErr := err != nil 83 | if tt.wantErr != gotErr { 84 | t.Errorf("typeSpec(%q, %q).err=%v want %s", tt.path, tt.typ, err, errBool(tt.wantErr)) 85 | continue 86 | } 87 | if err == nil { 88 | if reflect.DeepEqual(p, Pkg{}) { 89 | t.Errorf("typeSpec(%q, %q).pkg=Pkg{} want non-nil", tt.path, tt.typ) 90 | } 91 | if reflect.DeepEqual(spec, Spec{}) { 92 | t.Errorf("typeSpec(%q, %q).spec=Spec{} want non-nil", tt.path, tt.typ) 93 | } 94 | } 95 | } 96 | } 97 | 98 | func TestFuncs(t *testing.T) { 99 | t.Parallel() 100 | cases := []struct { 101 | iface string 102 | comments EmitComments 103 | want []Func 104 | wantErr bool 105 | }{ 106 | { 107 | iface: "io.ReadWriter", 108 | want: []Func{ 109 | { 110 | Name: "Read", 111 | Params: []Param{{Name: "p", Type: "[]byte"}}, 112 | Res: []Param{ 113 | {Name: "n", Type: "int"}, 114 | {Name: "err", Type: "error"}, 115 | }, 116 | }, 117 | { 118 | Name: "Write", 119 | Params: []Param{{Name: "p", Type: "[]byte"}}, 120 | Res: []Param{ 121 | {Name: "n", Type: "int"}, 122 | {Name: "err", Type: "error"}, 123 | }, 124 | }, 125 | }, 126 | comments: WithComments, 127 | }, 128 | { 129 | iface: "http.ResponseWriter", 130 | want: []Func{ 131 | { 132 | Name: "Header", 133 | Res: []Param{{Type: "http.Header"}}, 134 | }, 135 | { 136 | Name: "Write", 137 | Params: []Param{{Name: "_", Type: "[]byte"}}, 138 | Res: []Param{{Type: "int"}, {Type: "error"}}, 139 | }, 140 | { 141 | Name: "WriteHeader", 142 | Params: []Param{{Type: "int", Name: "statusCode"}}, 143 | }, 144 | }, 145 | comments: WithComments, 146 | }, 147 | { 148 | iface: "http.Handler", 149 | want: []Func{ 150 | { 151 | Name: "ServeHTTP", 152 | Params: []Param{ 153 | {Name: "_", Type: "http.ResponseWriter"}, 154 | {Name: "_", Type: "*http.Request"}, 155 | }, 156 | }, 157 | }, 158 | comments: WithComments, 159 | }, 160 | { 161 | iface: "ast.Node", 162 | want: []Func{ 163 | { 164 | Name: "Pos", 165 | Res: []Param{{Type: "token.Pos"}}, 166 | }, 167 | { 168 | Name: "End", 169 | Res: []Param{{Type: "token.Pos"}}, 170 | }, 171 | }, 172 | comments: WithComments, 173 | }, 174 | { 175 | iface: "cipher.AEAD", 176 | want: []Func{ 177 | { 178 | Name: "NonceSize", 179 | Res: []Param{{Type: "int"}}, 180 | }, 181 | { 182 | Name: "Overhead", 183 | Res: []Param{{Type: "int"}}, 184 | }, 185 | { 186 | Name: "Seal", 187 | Params: []Param{ 188 | {Name: "dst", Type: "[]byte"}, 189 | {Name: "nonce", Type: "[]byte"}, 190 | {Name: "plaintext", Type: "[]byte"}, 191 | {Name: "additionalData", Type: "[]byte"}, 192 | }, 193 | Res: []Param{{Type: "[]byte"}}, 194 | }, 195 | { 196 | Name: "Open", 197 | Params: []Param{ 198 | {Name: "dst", Type: "[]byte"}, 199 | {Name: "nonce", Type: "[]byte"}, 200 | {Name: "ciphertext", Type: "[]byte"}, 201 | {Name: "additionalData", Type: "[]byte"}, 202 | }, 203 | Res: []Param{{Type: "[]byte"}, {Type: "error"}}, 204 | }, 205 | }, 206 | comments: WithComments, 207 | }, 208 | { 209 | iface: "error", 210 | want: []Func{ 211 | { 212 | Name: "Error", 213 | Res: []Param{{Type: "string"}}, 214 | }, 215 | }, 216 | comments: WithComments, 217 | }, 218 | { 219 | iface: "error", 220 | want: []Func{ 221 | { 222 | Name: "Error", 223 | Res: []Param{{Type: "string"}}, 224 | }, 225 | }, 226 | comments: WithComments, 227 | }, 228 | { 229 | iface: "http.Flusher", 230 | want: []Func{ 231 | { 232 | Name: "Flush", 233 | Comments: "// Flush sends any buffered data to the client.\n", 234 | }, 235 | }, 236 | comments: WithComments, 237 | }, 238 | { 239 | iface: "http.Flusher", 240 | want: []Func{ 241 | { 242 | Name: "Flush", 243 | }, 244 | }, 245 | comments: WithoutComments, 246 | }, 247 | { 248 | iface: "net.Listener", 249 | want: []Func{ 250 | { 251 | Name: "Accept", 252 | Res: []Param{{Type: "net.Conn"}, {Type: "error"}}, 253 | }, 254 | { 255 | Name: "Close", 256 | Res: []Param{{Type: "error"}}, 257 | }, 258 | { 259 | Name: "Addr", 260 | Res: []Param{{Type: "net.Addr"}}, 261 | }, 262 | }, 263 | comments: WithComments, 264 | }, 265 | {iface: "net.Tennis", wantErr: true}, 266 | { 267 | iface: "github.com/josharian/impl/testdata.GenericInterface1[int]", 268 | want: []Func{ 269 | { 270 | Name: "Method1", 271 | Res: []Param{{Type: "int"}}, 272 | }, 273 | { 274 | Name: "Method2", 275 | Params: []Param{{Name: "_", Type: "int"}}, 276 | }, 277 | { 278 | Name: "Method3", 279 | Params: []Param{{Name: "_", Type: "int"}}, 280 | Res: []Param{{Type: "int"}}, 281 | }, 282 | }, 283 | comments: WithComments, 284 | }, 285 | } 286 | 287 | for _, tt := range cases { 288 | tt := tt 289 | t.Run(tt.iface, func(t *testing.T) { 290 | t.Parallel() 291 | fns, err := funcs(tt.iface, "", "", tt.comments) 292 | gotErr := err != nil 293 | if tt.wantErr != gotErr { 294 | t.Fatalf("funcs(%q).err=%v want %s", tt.iface, err, errBool(tt.wantErr)) 295 | } 296 | 297 | if len(fns) != len(tt.want) { 298 | t.Errorf("funcs(%q).fns=\n%v\nwant\n%v\n", tt.iface, fns, tt.want) 299 | } 300 | for i, fn := range fns { 301 | if fn.Name != tt.want[i].Name || 302 | !reflect.DeepEqual(fn.Params, tt.want[i].Params) || 303 | !reflect.DeepEqual(fn.Res, tt.want[i].Res) { 304 | 305 | t.Errorf("funcs(%q).fns=\n%v\nwant\n%v\n", tt.iface, fns, tt.want) 306 | } 307 | if tt.comments == WithoutComments && fn.Comments != "" { 308 | t.Errorf("funcs(%q).comments=\n%v\nbut comments disabled", tt.iface, fns) 309 | } 310 | } 311 | }) 312 | } 313 | } 314 | 315 | func TestValidReceiver(t *testing.T) { 316 | cases := []struct { 317 | recv string 318 | want bool 319 | }{ 320 | {recv: "f", want: true}, 321 | {recv: "f[T]", want: true}, 322 | {recv: "f[T, U]", want: true}, 323 | {recv: "F", want: true}, 324 | {recv: "*F[T]", want: true}, 325 | {recv: "*F[T, U]", want: true}, 326 | {recv: "f F", want: true}, 327 | {recv: "f *F", want: true}, 328 | {recv: "f *F[T]", want: true}, 329 | {recv: "f *F[T, U]", want: true}, 330 | {recv: "", want: false}, 331 | {recv: "a+b", want: false}, 332 | {recv: "[T]", want: false}, 333 | {recv: "[T, U]", want: false}, 334 | } 335 | 336 | for _, tt := range cases { 337 | got := validReceiver(tt.recv) 338 | if got != tt.want { 339 | t.Errorf("validReceiver(%q)=%t want %t", tt.recv, got, tt.want) 340 | } 341 | } 342 | } 343 | 344 | func TestValidMethodComments(t *testing.T) { 345 | cases := []struct { 346 | iface string 347 | want []Func 348 | }{ 349 | { 350 | iface: "github.com/josharian/impl/testdata.Interface1", 351 | want: []Func{ 352 | { 353 | Name: "Method1", 354 | Params: []Param{ 355 | { 356 | Name: "arg1", 357 | Type: "string", 358 | }, { 359 | Name: "arg2", 360 | Type: "string", 361 | }, 362 | }, 363 | Res: []Param{ 364 | { 365 | Name: "result", 366 | Type: "string", 367 | }, 368 | { 369 | Name: "err", 370 | Type: "error", 371 | }, 372 | }, Comments: "// Method1 is the first method of Interface1.\n", 373 | }, 374 | { 375 | Name: "Method2", 376 | Params: []Param{ 377 | { 378 | Name: "arg1", 379 | Type: "int", 380 | }, 381 | { 382 | Name: "arg2", 383 | Type: "int", 384 | }, 385 | }, 386 | Res: []Param{ 387 | { 388 | Name: "result", 389 | Type: "int", 390 | }, 391 | { 392 | Name: "err", 393 | Type: "error", 394 | }, 395 | }, 396 | Comments: "// Method2 is the second method of Interface1.\n", 397 | }, 398 | { 399 | Name: "Method3", 400 | Params: []Param{ 401 | { 402 | Name: "arg1", 403 | Type: "bool", 404 | }, 405 | { 406 | Name: "arg2", 407 | Type: "bool", 408 | }, 409 | }, 410 | Res: []Param{ 411 | { 412 | Name: "result", 413 | Type: "bool", 414 | }, 415 | { 416 | Name: "err", 417 | Type: "error", 418 | }, 419 | }, 420 | Comments: "// Method3 is the third method of Interface1.\n", 421 | }, 422 | }, 423 | }, 424 | { 425 | iface: "github.com/josharian/impl/testdata.Interface2", 426 | want: []Func{ 427 | { 428 | Name: "Method1", 429 | Params: []Param{ 430 | { 431 | Name: "arg1", 432 | Type: "int64", 433 | }, 434 | { 435 | Name: "arg2", 436 | Type: "int64", 437 | }, 438 | }, 439 | Res: []Param{ 440 | { 441 | Name: "result", 442 | Type: "int64", 443 | }, 444 | { 445 | Name: "err", 446 | Type: "error", 447 | }, 448 | }, 449 | Comments: "/*\n\t\tMethod1 is the first method of Interface2.\n\t*/\n", 450 | }, 451 | { 452 | Name: "Method2", 453 | Params: []Param{ 454 | { 455 | Name: "arg1", 456 | Type: "float64", 457 | }, 458 | { 459 | Name: "arg2", 460 | Type: "float64", 461 | }, 462 | }, 463 | Res: []Param{ 464 | { 465 | Name: "result", 466 | Type: "float64", 467 | }, 468 | { 469 | Name: "err", 470 | Type: "error", 471 | }, 472 | }, 473 | Comments: "/*\n\t\tMethod2 is the second method of Interface2.\n\t*/\n", 474 | }, 475 | { 476 | Name: "Method3", 477 | Params: []Param{ 478 | { 479 | Name: "arg1", 480 | Type: "interface{}", 481 | }, 482 | { 483 | Name: "arg2", 484 | Type: "interface{}", 485 | }, 486 | }, 487 | Res: []Param{ 488 | { 489 | Name: "result", 490 | Type: "interface{}", 491 | }, 492 | { 493 | Name: "err", 494 | Type: "error", 495 | }, 496 | }, 497 | Comments: "/*\n\t\tMethod3 is the third method of Interface2.\n\t*/\n", 498 | }, 499 | }, 500 | }, 501 | { 502 | iface: "github.com/josharian/impl/testdata.Interface3", 503 | want: []Func{ 504 | { 505 | Name: "Method1", 506 | Params: []Param{ 507 | { 508 | Name: "_", 509 | Type: "string", 510 | }, { 511 | Name: "_", 512 | Type: "string", 513 | }, 514 | }, 515 | Res: []Param{ 516 | { 517 | Name: "", 518 | Type: "string", 519 | }, 520 | { 521 | Name: "", 522 | Type: "error", 523 | }, 524 | }, Comments: "// Method1 is the first method of Interface3.\n", 525 | }, 526 | { 527 | Name: "Method2", 528 | Params: []Param{ 529 | { 530 | Name: "_", 531 | Type: "int", 532 | }, 533 | { 534 | Name: "arg2", 535 | Type: "int", 536 | }, 537 | }, 538 | Res: []Param{ 539 | { 540 | Name: "_", 541 | Type: "int", 542 | }, 543 | { 544 | Name: "err", 545 | Type: "error", 546 | }, 547 | }, 548 | Comments: "// Method2 is the second method of Interface3.\n", 549 | }, 550 | { 551 | Name: "Method3", 552 | Params: []Param{ 553 | { 554 | Name: "arg1", 555 | Type: "bool", 556 | }, 557 | { 558 | Name: "arg2", 559 | Type: "bool", 560 | }, 561 | }, 562 | Res: []Param{ 563 | { 564 | Name: "result1", 565 | Type: "bool", 566 | }, 567 | { 568 | Name: "result2", 569 | Type: "bool", 570 | }, 571 | }, 572 | Comments: "// Method3 is the third method of Interface3.\n", 573 | }, 574 | }, 575 | }, 576 | } 577 | 578 | for _, tt := range cases { 579 | fns, err := funcs(tt.iface, ".", "", WithComments) 580 | if err != nil { 581 | t.Errorf("funcs(%q).err=%v", tt.iface, err) 582 | } 583 | if !reflect.DeepEqual(fns, tt.want) { 584 | t.Errorf("funcs(%q).fns=\n%v\nwant\n%v\n", tt.iface, fns, tt.want) 585 | } 586 | } 587 | } 588 | 589 | func TestStubGeneration(t *testing.T) { 590 | cases := []struct { 591 | iface string 592 | want string 593 | dir string 594 | }{ 595 | { 596 | iface: "github.com/josharian/impl/testdata.Interface1", 597 | want: testdata.Interface1Output, 598 | dir: ".", 599 | }, 600 | { 601 | iface: "github.com/josharian/impl/testdata.Interface2", 602 | want: testdata.Interface2Output, 603 | dir: ".", 604 | }, 605 | { 606 | iface: "github.com/josharian/impl/testdata.Interface3", 607 | want: testdata.Interface3Output, 608 | dir: ".", 609 | }, 610 | { 611 | iface: "Interface1", 612 | want: testdata.Interface1Output, 613 | dir: "testdata", 614 | }, 615 | { 616 | iface: "github.com/josharian/impl/testdata.Interface9", 617 | want: testdata.Interface9Output, 618 | dir: ".", 619 | }, 620 | { 621 | iface: "github.com/josharian/impl/testdata.GenericInterface1[string]", 622 | want: testdata.GenericInterface1Output, 623 | dir: ".", 624 | }, 625 | { 626 | iface: "GenericInterface1[string]", 627 | want: testdata.GenericInterface1Output, 628 | dir: "testdata", 629 | }, 630 | { 631 | iface: "github.com/josharian/impl/testdata.GenericInterface2[string, bool]", 632 | want: testdata.GenericInterface2Output, 633 | dir: ".", 634 | }, 635 | { 636 | iface: "GenericInterface2[string, bool]", 637 | want: testdata.GenericInterface2Output, 638 | dir: "testdata", 639 | }, 640 | { 641 | iface: "github.com/josharian/impl/testdata.GenericInterface3[string, bool]", 642 | want: testdata.GenericInterface3Output, 643 | dir: ".", 644 | }, 645 | { 646 | iface: "GenericInterface3[string, bool]", 647 | want: testdata.GenericInterface3Output, 648 | dir: "testdata", 649 | }, 650 | } 651 | for _, tt := range cases { 652 | t.Run(tt.iface, func(t *testing.T) { 653 | fns, err := funcs(tt.iface, tt.dir, "", WithComments) 654 | if err != nil { 655 | t.Errorf("funcs(%q).err=%v", tt.iface, err) 656 | } 657 | src := genStubs("r *Receiver", fns, nil) 658 | if string(src) != tt.want { 659 | t.Errorf("genStubs(\"r *Receiver\", %+#v).src=\n%#v\nwant\n%#v\n", fns, string(src), tt.want) 660 | } 661 | }) 662 | } 663 | } 664 | 665 | func TestStubGenerationForImplemented(t *testing.T) { 666 | cases := []struct { 667 | desc string 668 | iface string 669 | recv string 670 | recvPkg string 671 | want string 672 | }{ 673 | { 674 | desc: "without implemeted methods", 675 | iface: "github.com/josharian/impl/testdata.Interface3", 676 | recv: "r *Implemented", 677 | recvPkg: "testdata", 678 | want: testdata.Interface4Output, 679 | }, 680 | { 681 | desc: "without implemeted methods with trailing space", 682 | iface: "github.com/josharian/impl/testdata.Interface3", 683 | recv: "r *Implemented ", 684 | recvPkg: "testdata", 685 | want: testdata.Interface4Output, 686 | }, 687 | { 688 | desc: "without implemeted methods, with generic receiver", 689 | iface: "github.com/josharian/impl/testdata.Interface3", 690 | recv: "r *ImplementedGeneric[Type1]", 691 | recvPkg: "testdata", 692 | want: testdata.Interface4GenericOutput, 693 | }, 694 | { 695 | desc: "without implemeted methods, with generic receiver with multiple params", 696 | iface: "github.com/josharian/impl/testdata.Interface3", 697 | recv: "r *ImplementedGenericMultipleParams[Type1, Type2]", 698 | recvPkg: "testdata", 699 | want: testdata.Interface4GenericMultipleParamsOutput, 700 | }, 701 | { 702 | desc: "without implemeted methods and receiver variable", 703 | iface: "github.com/josharian/impl/testdata.Interface3", 704 | recv: "*Implemented", 705 | recvPkg: "testdata", 706 | want: strings.ReplaceAll(testdata.Interface4Output, "r *Implemented", "*Implemented"), 707 | }, 708 | { 709 | desc: "receiver and interface in the same package", 710 | iface: "github.com/josharian/impl/testdata.Interface5", 711 | recv: "r *Implemented", 712 | recvPkg: "testdata", 713 | want: testdata.Interface5Output, 714 | }, 715 | { 716 | desc: "generic receiver and interface in the same package", 717 | iface: "github.com/josharian/impl/testdata.Interface5", 718 | recv: "r *ImplementedGeneric[Type1]", 719 | recvPkg: "testdata", 720 | want: testdata.Interface5GenericOutput, 721 | }, 722 | { 723 | desc: "generic receiver with multiple params and interface in the same package", 724 | iface: "github.com/josharian/impl/testdata.Interface5", 725 | recv: "r *ImplementedGenericMultipleParams[Type1, Type2]", 726 | recvPkg: "testdata", 727 | want: testdata.Interface5GenericMultipleParamsOutput, 728 | }, 729 | { 730 | desc: "receiver and interface in a different package", 731 | iface: "github.com/josharian/impl/testdata.Interface5", 732 | recv: "r *Implemented", 733 | recvPkg: "test", 734 | want: testdata.Interface6Output, 735 | }, 736 | { 737 | desc: "generic receiver and interface in a different package", 738 | iface: "github.com/josharian/impl/testdata.Interface5", 739 | recv: "r *ImplementedGeneric[Type1]", 740 | recvPkg: "test", 741 | want: testdata.Interface6GenericOutput, 742 | }, 743 | { 744 | desc: "generic receiver with multiple params and interface in a different package", 745 | iface: "github.com/josharian/impl/testdata.Interface5", 746 | recv: "r *ImplementedGenericMultipleParams[Type1, Type2]", 747 | recvPkg: "test", 748 | want: testdata.Interface6GenericMultipleParamsOutput, 749 | }, 750 | } 751 | for _, tt := range cases { 752 | t.Run(tt.desc, func(t *testing.T) { 753 | fns, err := funcs(tt.iface, ".", tt.recvPkg, WithComments) 754 | if err != nil { 755 | t.Errorf("funcs(%q).err=%v", tt.iface, err) 756 | } 757 | 758 | implemented, err := implementedFuncs(fns, tt.recv, "testdata") 759 | if err != nil { 760 | t.Errorf("ifuncs.err=%v", err) 761 | } 762 | src := genStubs(tt.recv, fns, implemented) 763 | if string(src) != tt.want { 764 | t.Errorf("genStubs(\"r *Implemented\", %+#v).src=\n\n%#v\n\nwant\n\n%#v\n\n", fns, string(src), tt.want) 765 | } 766 | }) 767 | } 768 | } 769 | 770 | func TestStubGenerationForRepeatedName(t *testing.T) { 771 | cases := []struct { 772 | desc string 773 | iface string 774 | recv string 775 | recvPkg string 776 | want string 777 | }{ 778 | { 779 | desc: "receiver and in.Params with the same name", 780 | iface: "github.com/josharian/impl/testdata.Interface6", 781 | recv: "arg1 *Implemented", 782 | recvPkg: "testdata", 783 | want: testdata.Interface7Output, 784 | }, 785 | { 786 | desc: "receiver and out.Params with the same name", 787 | iface: "github.com/josharian/impl/testdata.Interface6", 788 | recv: "arg3 *Implemented", 789 | recvPkg: "testdata", 790 | want: testdata.Interface8Output, 791 | }, 792 | } 793 | for _, tt := range cases { 794 | t.Run(tt.desc, func(t *testing.T) { 795 | fns, err := funcs(tt.iface, ".", tt.recvPkg, WithComments) 796 | if err != nil { 797 | t.Errorf("funcs(%q).err=%v", tt.iface, err) 798 | } 799 | 800 | implemented, err := implementedFuncs(fns, tt.recv, "testdata") 801 | if err != nil { 802 | t.Errorf("ifuncs.err=%v", err) 803 | } 804 | src := genStubs(tt.recv, fns, implemented) 805 | if string(src) != tt.want { 806 | t.Errorf("genStubs(\"r *Implemented\", %+#v).src=\n\n%#v\n\nwant\n\n%#v\n\n", fns, string(src), tt.want) 807 | } 808 | }) 809 | } 810 | } 811 | 812 | func TestParseTypeParams(t *testing.T) { 813 | t.Parallel() 814 | 815 | cases := []struct { 816 | desc string 817 | input string 818 | want Type 819 | wantErr bool 820 | }{ 821 | {desc: "non-generic type", input: "Reader", want: Type{Name: "Reader"}}, 822 | {desc: "one type param", input: "Reader[Foo]", want: Type{Name: "Reader", Params: []string{"Foo"}}}, 823 | {desc: "two type params", input: "Reader[Foo, Bar]", want: Type{Name: "Reader", Params: []string{"Foo", "Bar"}}}, 824 | {desc: "three type params", input: "Reader[Foo, Bar, Baz]", want: Type{Name: "Reader", Params: []string{"Foo", "Bar", "Baz"}}}, 825 | {desc: "no spaces", input: "Reader[Foo,Bar]", want: Type{Name: "Reader", Params: []string{"Foo", "Bar"}}}, 826 | {desc: "unclosed brackets", input: "Reader[Foo", wantErr: true}, 827 | {desc: "no params", input: "Reader[]", wantErr: true}, 828 | {desc: "space-only params", input: "Reader[ ]", wantErr: true}, 829 | {desc: "multiple space-only params", input: "Reader[ , , ]", wantErr: true}, 830 | {desc: "characters after bracket", input: "Reader[Foo]Bar", wantErr: true}, 831 | {desc: "qualified generic type", input: "io.Reader[Foo]", want: Type{Name: "io.Reader", Params: []string{"Foo"}}}, 832 | {desc: "qualified generic type with two params", input: "io.Reader[Foo, Bar]", want: Type{Name: "io.Reader", Params: []string{"Foo", "Bar"}}}, 833 | {desc: "qualified generic param", input: "Reader[io.Reader]", want: Type{Name: "Reader", Params: []string{"io.Reader"}}}, 834 | {desc: "qualified and unqualified generic param", input: "Reader[io.Reader, string]", want: Type{Name: "Reader", Params: []string{"io.Reader", "string"}}}, 835 | {desc: "pointer qualified generic param", input: "Reader[*io.Reader]", want: Type{Name: "Reader", Params: []string{"*io.Reader"}}}, 836 | {desc: "map generic param", input: "Reader[map[string]string]", want: Type{Name: "Reader", Params: []string{"map[string]string"}}}, 837 | {desc: "pointer map generic param", input: "Reader[*map[string]string]", want: Type{Name: "Reader", Params: []string{"*map[string]string"}}}, 838 | {desc: "pointer key map generic param", input: "Reader[map[*string]string]", want: Type{Name: "Reader", Params: []string{"map[*string]string"}}}, 839 | {desc: "pointer value map generic param", input: "Reader[map[string]*string]", want: Type{Name: "Reader", Params: []string{"map[string]*string"}}}, 840 | {desc: "slice generic param", input: "Reader[[]string]", want: Type{Name: "Reader", Params: []string{"[]string"}}}, 841 | {desc: "pointer slice generic param", input: "Reader[*[]string]", want: Type{Name: "Reader", Params: []string{"*[]string"}}}, 842 | {desc: "pointer slice value generic param", input: "Reader[[]*string]", want: Type{Name: "Reader", Params: []string{"[]*string"}}}, 843 | {desc: "array generic param", input: "Reader[[1]string]", want: Type{Name: "Reader", Params: []string{"[1]string"}}}, 844 | {desc: "pointer array generic param", input: "Reader[*[1]string]", want: Type{Name: "Reader", Params: []string{"*[1]string"}}}, 845 | {desc: "pointer array value generic param", input: "Reader[[1]*string]", want: Type{Name: "Reader", Params: []string{"[1]*string"}}}, 846 | {desc: "chan generic param", input: "Reader[chan error]", want: Type{Name: "Reader", Params: []string{"chan error"}}}, 847 | {desc: "receiver chan generic param", input: "Reader[<-chan error]", want: Type{Name: "Reader", Params: []string{"<-chan error"}}}, 848 | {desc: "send chan generic param", input: "Reader[chan<- error]", want: Type{Name: "Reader", Params: []string{"chan<- error"}}}, 849 | {desc: "pointer chan generic param", input: "Reader[*chan error]", want: Type{Name: "Reader", Params: []string{"*chan error"}}}, 850 | {desc: "func generic param", input: "Reader[func() string]", want: Type{Name: "Reader", Params: []string{"func() string"}}}, 851 | {desc: "one arg func generic param", input: "Reader[func(a int) string]", want: Type{Name: "Reader", Params: []string{"func(a int) string"}}}, 852 | {desc: "two arg one type func generic param", input: "Reader[func(a, b int) string]", want: Type{Name: "Reader", Params: []string{"func(a, b int) string"}}}, 853 | {desc: "three arg one type func generic param", input: "Reader[func(a, b, c int) string]", want: Type{Name: "Reader", Params: []string{"func(a, b, c int) string"}}}, 854 | {desc: "three arg two types func generic param", input: "Reader[func(a, b string, c int) string]", want: Type{Name: "Reader", Params: []string{"func(a, b string, c int) string"}}}, 855 | {desc: "three arg three types func generic param", input: "Reader[func(a bool, b string, c int) string]", want: Type{Name: "Reader", Params: []string{"func(a bool, b string, c int) string"}}}, 856 | // don't need support for generics on the function type itself; function types must have no type parameters 857 | // https://cs.opensource.google/go/go/+/master:src/go/parser/parser.go;l=1048;drc=cafb49ac731f862f386862d64b27b8314eeb2909 858 | } 859 | for _, tt := range cases { 860 | tt := tt 861 | t.Run(tt.desc, func(t *testing.T) { 862 | t.Parallel() 863 | 864 | typ, err := parseType(tt.input) 865 | if err != nil { 866 | if tt.wantErr { 867 | return 868 | } 869 | t.Fatalf("unexpected error: %s", err) 870 | } 871 | if typ.Name != tt.want.Name { 872 | t.Errorf("wanted ID %q, got %q", tt.want.Name, typ.Name) 873 | } 874 | if len(typ.Params) != len(tt.want.Params) { 875 | t.Errorf("wanted %d params, got %d: %v", len(tt.want.Params), len(typ.Params), typ.Params) 876 | } 877 | for pos, param := range typ.Params { 878 | if param != tt.want.Params[pos] { 879 | t.Errorf("expected param %d to be %q, got %q: %v", pos, tt.want.Params[pos], param, typ.Params) 880 | } 881 | } 882 | }) 883 | } 884 | } 885 | -------------------------------------------------------------------------------- /implemented.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "strings" 9 | ) 10 | 11 | // implementedFuncs returns list of Func which already implemented. 12 | func implementedFuncs(fns []Func, recv string, srcDir string) (map[string]bool, error) { 13 | 14 | // determine name of receiver type 15 | recvType := getReceiverType(recv) 16 | 17 | fset := token.NewFileSet() 18 | pkgs, err := parser.ParseDir(fset, srcDir, nil, 0) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | implemented := make(map[string]bool) 24 | 25 | // getReceiver returns title of struct to which belongs the method 26 | getReceiver := func(mf *ast.FuncDecl) string { 27 | if mf.Recv == nil { 28 | return "" 29 | } 30 | 31 | for _, v := range mf.Recv.List { 32 | switch xv := v.Type.(type) { 33 | case *ast.StarExpr: 34 | switch xxv := xv.X.(type) { 35 | case *ast.Ident: 36 | return xxv.Name 37 | case *ast.IndexExpr: // type with one type parameter. 38 | if si, ok := xxv.X.(*ast.Ident); ok { 39 | return si.Name 40 | } 41 | case *ast.IndexListExpr: // type with mutiple type parameters. 42 | if si, ok := xxv.X.(*ast.Ident); ok { 43 | return si.Name 44 | } 45 | } 46 | case *ast.Ident: 47 | return xv.Name 48 | } 49 | } 50 | 51 | return "" 52 | } 53 | 54 | // Convert fns to a map, to prevent accidental quadratic behavior. 55 | want := make(map[string]bool) 56 | for _, fn := range fns { 57 | want[fn.Name] = true 58 | } 59 | 60 | // finder is a walker func which will be called for each element in the source code of package 61 | // but we are interested in funcs only with receiver same to typeTitle 62 | finder := func(n ast.Node) bool { 63 | x, ok := n.(*ast.FuncDecl) 64 | if !ok { 65 | return true 66 | } 67 | if getReceiver(x) != recvType { 68 | return true 69 | } 70 | name := x.Name.String() 71 | if want[name] { 72 | implemented[name] = true 73 | } 74 | return true 75 | } 76 | 77 | for _, pkg := range pkgs { 78 | for _, f := range pkg.Files { 79 | ast.Inspect(f, finder) 80 | } 81 | } 82 | 83 | return implemented, nil 84 | } 85 | 86 | // getReceiverType returns type name of receiver or fatal if receiver is invalid. 87 | // ex: for definition "r *SomeType" will return "SomeType" 88 | func getReceiverType(recv string) string { 89 | var recvType string 90 | 91 | // VSCode adds a trailing space to receiver (it runs impl like: impl 'r *Receiver ' io.Writer) 92 | // so we have to remove spaces. 93 | recv = strings.TrimSpace(recv) 94 | 95 | // Remove type parameters. They can contain spaces too, for example 'r *Receiver[T, U]'. 96 | recv, _, _ = strings.Cut(recv, "[") 97 | 98 | parts := strings.Split(recv, " ") 99 | switch len(parts) { 100 | case 1: // (SomeType) 101 | recvType = parts[0] 102 | case 2: // (x SomeType) 103 | recvType = parts[1] 104 | default: 105 | fatal(fmt.Sprintf("invalid receiver: %q", recv)) 106 | } 107 | 108 | // Pointer to receiver should be removed too for comparison purpose. 109 | // But don't worry definition of default receiver won't be changed. 110 | return strings.TrimPrefix(recvType, "*") 111 | } 112 | -------------------------------------------------------------------------------- /testdata/free_floating.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // Interface9Output is the expected output generated from reflecting on 4 | // Interface9, provided that the receiver is equal to 'r *Receiver'. 5 | var Interface9Output = `// Method1 is the first method of Interface1. 6 | // line two 7 | func (r *Receiver) Method1(arg1 string, arg2 string) (result string, err error) { 8 | panic("not implemented") // TODO: Implement 9 | } 10 | 11 | ` 12 | 13 | // Interface9 is a dummy interface to test the program output. 14 | // This interface tests free-floating comments 15 | type Interface9 interface { 16 | // free-floating comment before Method1 17 | 18 | // Method1 is the first method of Interface1. 19 | // line two 20 | Method1(arg1 string, arg2 string) (result string, err error) 21 | 22 | // free-floating comment after Method1 23 | } 24 | 25 | // free-floating comment at end of file. This must be the last comment in this file. 26 | -------------------------------------------------------------------------------- /testdata/interfaces.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // Interface1 is a dummy interface to test the program output. 4 | // This interface tests //-style method comments. 5 | type Interface1 interface { 6 | // Method1 is the first method of Interface1. 7 | Method1(arg1 string, arg2 string) (result string, err error) 8 | // Method2 is the second method of Interface1. 9 | Method2(arg1 int, arg2 int) (result int, err error) 10 | // Method3 is the third method of Interface1. 11 | Method3(arg1 bool, arg2 bool) (result bool, err error) 12 | } 13 | 14 | // Interface2 is a dummy interface to test the program output. 15 | // This interface tests /*-style method comments. 16 | type Interface2 interface { 17 | /* 18 | Method1 is the first method of Interface2. 19 | */ 20 | Method1(arg1 int64, arg2 int64) (result int64, err error) 21 | /* 22 | Method2 is the second method of Interface2. 23 | */ 24 | Method2(arg1 float64, arg2 float64) (result float64, err error) 25 | /* 26 | Method3 is the third method of Interface2. 27 | */ 28 | Method3(arg1 interface{}, arg2 interface{}) (result interface{}, err error) 29 | } 30 | 31 | // Interface3 is a dummy interface to test the program output. This interface 32 | // tests generation of method parameters and results. 33 | // 34 | // The first method tests the generation of anonymous method paramaters and 35 | // results. 36 | // 37 | // The second method tests the generation of method parameters and results where 38 | // the blank identifier "_" is already used in the names. 39 | // 40 | // The third method tests the generation of method parameters and results that 41 | // are grouped by type. 42 | type Interface3 interface { 43 | // Method1 is the first method of Interface3. 44 | Method1(string, string) (string, error) 45 | // Method2 is the second method of Interface3. 46 | Method2(_ int, arg2 int) (_ int, err error) 47 | // Method3 is the third method of Interface3. 48 | Method3(arg1, arg2 bool) (result1, result2 bool) 49 | } 50 | 51 | // GenericInterface1 is a dummy interface to test the program output. This 52 | // interface tests generation of generic interfaces with the specified type 53 | // parameters. 54 | type GenericInterface1[Type any] interface { 55 | // Method1 is the first method of GenericInterface1. 56 | Method1() Type 57 | // Method2 is the second method of GenericInterface1. 58 | Method2(Type) 59 | // Method3 is the third method of GenericInterface1. 60 | Method3(Type) Type 61 | } 62 | 63 | // GenericInterface2 is a dummy interface to test the program output. This 64 | // interface tests generation of generic interfaces with the specified type 65 | // parameters. 66 | type GenericInterface2[Type1 any, Type2 comparable] interface { 67 | // Method1 is the first method of GenericInterface2. 68 | Method1() (Type1, Type2) 69 | // Method2 is the second method of GenericInterface2. 70 | Method2(Type1, Type2) 71 | // Method3 is the third method of GenericInterface2. 72 | Method3(Type1) Type2 73 | } 74 | 75 | // GenericInterface3 is a dummy interface to test the program output. This 76 | // interface tests generation of generic interfaces with repeated type 77 | // parameters. 78 | type GenericInterface3[Type1, Type2 any] interface { 79 | // Method1 is the first method of GenericInterface3. 80 | Method1() (Type1, Type2) 81 | // Method2 is the second method of GenericInterface3. 82 | Method2(Type1, Type2) 83 | // Method3 is the third method of GenericInterface3. 84 | Method3(Type1) Type2 85 | } 86 | 87 | // Interface1Output is the expected output generated from reflecting on 88 | // Interface1, provided that the receiver is equal to 'r *Receiver'. 89 | var Interface1Output = `// Method1 is the first method of Interface1. 90 | func (r *Receiver) Method1(arg1 string, arg2 string) (result string, err error) { 91 | panic("not implemented") // TODO: Implement 92 | } 93 | 94 | // Method2 is the second method of Interface1. 95 | func (r *Receiver) Method2(arg1 int, arg2 int) (result int, err error) { 96 | panic("not implemented") // TODO: Implement 97 | } 98 | 99 | // Method3 is the third method of Interface1. 100 | func (r *Receiver) Method3(arg1 bool, arg2 bool) (result bool, err error) { 101 | panic("not implemented") // TODO: Implement 102 | } 103 | 104 | ` 105 | 106 | // Interface2Output is the expected output generated from reflecting on 107 | // Interface2, provided that the receiver is equal to 'r *Receiver'. 108 | var Interface2Output = `/* 109 | Method1 is the first method of Interface2. 110 | */ 111 | func (r *Receiver) Method1(arg1 int64, arg2 int64) (result int64, err error) { 112 | panic("not implemented") // TODO: Implement 113 | } 114 | 115 | /* 116 | Method2 is the second method of Interface2. 117 | */ 118 | func (r *Receiver) Method2(arg1 float64, arg2 float64) (result float64, err error) { 119 | panic("not implemented") // TODO: Implement 120 | } 121 | 122 | /* 123 | Method3 is the third method of Interface2. 124 | */ 125 | func (r *Receiver) Method3(arg1 interface{}, arg2 interface{}) (result interface{}, err error) { 126 | panic("not implemented") // TODO: Implement 127 | } 128 | 129 | ` 130 | 131 | // Interface3Output is the expected output generated from reflecting on 132 | // Interface3, provided that the receiver is equal to 'r *Receiver'. 133 | var Interface3Output = `// Method1 is the first method of Interface3. 134 | func (r *Receiver) Method1(_ string, _ string) (string, error) { 135 | panic("not implemented") // TODO: Implement 136 | } 137 | 138 | // Method2 is the second method of Interface3. 139 | func (r *Receiver) Method2(_ int, arg2 int) (_ int, err error) { 140 | panic("not implemented") // TODO: Implement 141 | } 142 | 143 | // Method3 is the third method of Interface3. 144 | func (r *Receiver) Method3(arg1 bool, arg2 bool) (result1 bool, result2 bool) { 145 | panic("not implemented") // TODO: Implement 146 | } 147 | 148 | ` 149 | 150 | type Implemented struct{} 151 | 152 | func (r *Implemented) Method1(arg1 string, arg2 string) (result string, err error) { 153 | return "", nil 154 | } 155 | 156 | // Interface4Output is the expected output generated from reflecting on 157 | // Interface3, provided that the receiver is equal to 'r *Implemented'. 158 | var Interface4Output = `// Method2 is the second method of Interface3. 159 | func (r *Implemented) Method2(_ int, arg2 int) (_ int, err error) { 160 | panic("not implemented") // TODO: Implement 161 | } 162 | 163 | // Method3 is the third method of Interface3. 164 | func (r *Implemented) Method3(arg1 bool, arg2 bool) (result1 bool, result2 bool) { 165 | panic("not implemented") // TODO: Implement 166 | } 167 | 168 | ` 169 | 170 | type Struct5 struct { 171 | } 172 | 173 | type Interface5 interface { 174 | // Method is the first method of Interface5. 175 | Method2(arg1 string, arg2 Interface2, arg3 Struct5) (Interface3, error) 176 | } 177 | 178 | var Interface5Output = `// Method is the first method of Interface5. 179 | func (r *Implemented) Method2(arg1 string, arg2 Interface2, arg3 Struct5) (Interface3, error) { 180 | panic("not implemented") // TODO: Implement 181 | } 182 | 183 | ` 184 | 185 | // Interface6Output receiver not in current package 186 | var Interface6Output = `// Method is the first method of Interface5. 187 | func (r *Implemented) Method2(arg1 string, arg2 testdata.Interface2, arg3 testdata.Struct5) (testdata.Interface3, error) { 188 | panic("not implemented") // TODO: Implement 189 | } 190 | 191 | ` 192 | 193 | type Interface6 interface { 194 | // Method is the first method of Interface6. 195 | Method2(arg1 string, arg2 int) (arg3 error) 196 | } 197 | 198 | var Interface7Output = `// Method is the first method of Interface6. 199 | func (arg1 *Implemented) Method2(_ string, arg2 int) (arg3 error) { 200 | panic("not implemented") // TODO: Implement 201 | } 202 | 203 | ` 204 | 205 | var Interface8Output = `// Method is the first method of Interface6. 206 | func (arg3 *Implemented) Method2(arg1 string, arg2 int) (_ error) { 207 | panic("not implemented") // TODO: Implement 208 | } 209 | 210 | ` 211 | 212 | // GenericInterface1Output is the expected output generated from reflecting on 213 | // GenericInterface1, provided that the receiver is equal to 'r *Receiver' and 214 | // it was generated with the type parameters [string]. 215 | var GenericInterface1Output = `// Method1 is the first method of GenericInterface1. 216 | func (r *Receiver) Method1() string { 217 | panic("not implemented") // TODO: Implement 218 | } 219 | 220 | // Method2 is the second method of GenericInterface1. 221 | func (r *Receiver) Method2(_ string) { 222 | panic("not implemented") // TODO: Implement 223 | } 224 | 225 | // Method3 is the third method of GenericInterface1. 226 | func (r *Receiver) Method3(_ string) string { 227 | panic("not implemented") // TODO: Implement 228 | } 229 | 230 | ` 231 | 232 | // GenericInterface2Output is the expected output generated from reflecting on 233 | // GenericInterface2, provided that the receiver is equal to 'r *Receiver' and 234 | // it was generated with the type parameters [string, bool]. 235 | var GenericInterface2Output = `// Method1 is the first method of GenericInterface2. 236 | func (r *Receiver) Method1() (string, bool) { 237 | panic("not implemented") // TODO: Implement 238 | } 239 | 240 | // Method2 is the second method of GenericInterface2. 241 | func (r *Receiver) Method2(_ string, _ bool) { 242 | panic("not implemented") // TODO: Implement 243 | } 244 | 245 | // Method3 is the third method of GenericInterface2. 246 | func (r *Receiver) Method3(_ string) bool { 247 | panic("not implemented") // TODO: Implement 248 | } 249 | 250 | ` 251 | 252 | // GenericInterface3Output is the expected output generated from reflecting on 253 | // GenericInterface3, provided that the receiver is equal to 'r *Receiver' and 254 | // it was generated with the type parameters [string, bool]. 255 | var GenericInterface3Output = `// Method1 is the first method of GenericInterface3. 256 | func (r *Receiver) Method1() (string, bool) { 257 | panic("not implemented") // TODO: Implement 258 | } 259 | 260 | // Method2 is the second method of GenericInterface3. 261 | func (r *Receiver) Method2(_ string, _ bool) { 262 | panic("not implemented") // TODO: Implement 263 | } 264 | 265 | // Method3 is the third method of GenericInterface3. 266 | func (r *Receiver) Method3(_ string) bool { 267 | panic("not implemented") // TODO: Implement 268 | } 269 | 270 | ` 271 | 272 | type ImplementedGeneric[Type1 any] struct{} 273 | 274 | func (r *ImplementedGeneric[Type1]) Method1(arg1 string, arg2 string) (result string, err error) { 275 | return "", nil 276 | } 277 | 278 | var Interface4GenericOutput = `// Method2 is the second method of Interface3. 279 | func (r *ImplementedGeneric[Type1]) Method2(_ int, arg2 int) (_ int, err error) { 280 | panic("not implemented") // TODO: Implement 281 | } 282 | 283 | // Method3 is the third method of Interface3. 284 | func (r *ImplementedGeneric[Type1]) Method3(arg1 bool, arg2 bool) (result1 bool, result2 bool) { 285 | panic("not implemented") // TODO: Implement 286 | } 287 | 288 | ` 289 | 290 | var Interface5GenericOutput = `// Method is the first method of Interface5. 291 | func (r *ImplementedGeneric[Type1]) Method2(arg1 string, arg2 Interface2, arg3 Struct5) (Interface3, error) { 292 | panic("not implemented") // TODO: Implement 293 | } 294 | 295 | ` 296 | 297 | // Interface6GenericOutput receiver not in current package 298 | var Interface6GenericOutput = `// Method is the first method of Interface5. 299 | func (r *ImplementedGeneric[Type1]) Method2(arg1 string, arg2 testdata.Interface2, arg3 testdata.Struct5) (testdata.Interface3, error) { 300 | panic("not implemented") // TODO: Implement 301 | } 302 | 303 | ` 304 | 305 | type ImplementedGenericMultipleParams[Type1 any, Type2 comparable] struct{} 306 | 307 | func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method1(arg1 string, arg2 string) (result string, err error) { 308 | return "", nil 309 | } 310 | 311 | var Interface4GenericMultipleParamsOutput = `// Method2 is the second method of Interface3. 312 | func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method2(_ int, arg2 int) (_ int, err error) { 313 | panic("not implemented") // TODO: Implement 314 | } 315 | 316 | // Method3 is the third method of Interface3. 317 | func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method3(arg1 bool, arg2 bool) (result1 bool, result2 bool) { 318 | panic("not implemented") // TODO: Implement 319 | } 320 | 321 | ` 322 | 323 | var Interface5GenericMultipleParamsOutput = `// Method is the first method of Interface5. 324 | func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method2(arg1 string, arg2 Interface2, arg3 Struct5) (Interface3, error) { 325 | panic("not implemented") // TODO: Implement 326 | } 327 | 328 | ` 329 | 330 | // Interface6GenericMultipleParamsOutput receiver not in current package 331 | var Interface6GenericMultipleParamsOutput = `// Method is the first method of Interface5. 332 | func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method2(arg1 string, arg2 testdata.Interface2, arg3 testdata.Struct5) (testdata.Interface3, error) { 333 | panic("not implemented") // TODO: Implement 334 | } 335 | 336 | ` 337 | --------------------------------------------------------------------------------