diff --git a/impl_test.go b/impl_test.go index 8320e98..739a2a3 100644 --- a/impl_test.go +++ b/impl_test.go @@ -318,11 +318,19 @@ func TestValidReceiver(t *testing.T) { want bool }{ {recv: "f", want: true}, + {recv: "f[T]", want: true}, + {recv: "f[T, U]", want: true}, {recv: "F", want: true}, + {recv: "*F[T]", want: true}, + {recv: "*F[T, U]", want: true}, {recv: "f F", want: true}, {recv: "f *F", want: true}, + {recv: "f *F[T]", want: true}, + {recv: "f *F[T, U]", want: true}, {recv: "", want: false}, {recv: "a+b", want: false}, + {recv: "[T]", want: false}, + {recv: "[T, U]", want: false}, } for _, tt := range cases { @@ -676,6 +684,20 @@ func TestStubGenerationForImplemented(t *testing.T) { recvPkg: "testdata", want: testdata.Interface4Output, }, + { + desc: "without implemeted methods, with generic receiver", + iface: "github.com/josharian/impl/testdata.Interface3", + recv: "r *ImplementedGeneric[Type1]", + recvPkg: "testdata", + want: testdata.Interface4GenericOutput, + }, + { + desc: "without implemeted methods, with generic receiver with multiple params", + iface: "github.com/josharian/impl/testdata.Interface3", + recv: "r *ImplementedGenericMultipleParams[Type1, Type2]", + recvPkg: "testdata", + want: testdata.Interface4GenericMultipleParamsOutput, + }, { desc: "without implemeted methods and receiver variable", iface: "github.com/josharian/impl/testdata.Interface3", @@ -690,6 +712,20 @@ func TestStubGenerationForImplemented(t *testing.T) { recvPkg: "testdata", want: testdata.Interface5Output, }, + { + desc: "generic receiver and interface in the same package", + iface: "github.com/josharian/impl/testdata.Interface5", + recv: "r *ImplementedGeneric[Type1]", + recvPkg: "testdata", + want: testdata.Interface5GenericOutput, + }, + { + desc: "generic receiver with multiple params and interface in the same package", + iface: "github.com/josharian/impl/testdata.Interface5", + recv: "r *ImplementedGenericMultipleParams[Type1, Type2]", + recvPkg: "testdata", + want: testdata.Interface5GenericMultipleParamsOutput, + }, { desc: "receiver and interface in a different package", iface: "github.com/josharian/impl/testdata.Interface5", @@ -697,6 +733,20 @@ func TestStubGenerationForImplemented(t *testing.T) { recvPkg: "test", want: testdata.Interface6Output, }, + { + desc: "generic receiver and interface in a different package", + iface: "github.com/josharian/impl/testdata.Interface5", + recv: "r *ImplementedGeneric[Type1]", + recvPkg: "test", + want: testdata.Interface6GenericOutput, + }, + { + desc: "generic receiver with multiple params and interface in a different package", + iface: "github.com/josharian/impl/testdata.Interface5", + recv: "r *ImplementedGenericMultipleParams[Type1, Type2]", + recvPkg: "test", + want: testdata.Interface6GenericMultipleParamsOutput, + }, } for _, tt := range cases { t.Run(tt.desc, func(t *testing.T) { diff --git a/implemented.go b/implemented.go index 5df78a7..2c55b88 100644 --- a/implemented.go +++ b/implemented.go @@ -31,8 +31,17 @@ func implementedFuncs(fns []Func, recv string, srcDir string) (map[string]bool, for _, v := range mf.Recv.List { switch xv := v.Type.(type) { case *ast.StarExpr: - if si, ok := xv.X.(*ast.Ident); ok { - return si.Name + switch xxv := xv.X.(type) { + case *ast.Ident: + return xxv.Name + case *ast.IndexExpr: // type with one type parameter. + if si, ok := xxv.X.(*ast.Ident); ok { + return si.Name + } + case *ast.IndexListExpr: // type with mutiple type parameters. + if si, ok := xxv.X.(*ast.Ident); ok { + return si.Name + } } case *ast.Ident: return xv.Name @@ -82,6 +91,10 @@ func getReceiverType(recv string) string { // VSCode adds a trailing space to receiver (it runs impl like: impl 'r *Receiver ' io.Writer) // so we have to remove spaces. recv = strings.TrimSpace(recv) + + // Remove type parameters. They can contain spaces too, for example 'r *Receiver[T, U]'. + recv, _, _ = strings.Cut(recv, "[") + parts := strings.Split(recv, " ") switch len(parts) { case 1: // (SomeType) diff --git a/testdata/interfaces.go b/testdata/interfaces.go index 3b9f25b..d42d57d 100644 --- a/testdata/interfaces.go +++ b/testdata/interfaces.go @@ -268,3 +268,69 @@ func (r *Receiver) Method3(_ string) bool { } ` + +type ImplementedGeneric[Type1 any] struct{} + +func (r *ImplementedGeneric[Type1]) Method1(arg1 string, arg2 string) (result string, err error) { + return "", nil +} + +var Interface4GenericOutput = `// Method2 is the second method of Interface3. +func (r *ImplementedGeneric[Type1]) Method2(_ int, arg2 int) (_ int, err error) { + panic("not implemented") // TODO: Implement +} + +// Method3 is the third method of Interface3. +func (r *ImplementedGeneric[Type1]) Method3(arg1 bool, arg2 bool) (result1 bool, result2 bool) { + panic("not implemented") // TODO: Implement +} + +` + +var Interface5GenericOutput = `// Method is the first method of Interface5. +func (r *ImplementedGeneric[Type1]) Method2(arg1 string, arg2 Interface2, arg3 Struct5) (Interface3, error) { + panic("not implemented") // TODO: Implement +} + +` + +// Interface6GenericOutput receiver not in current package +var Interface6GenericOutput = `// Method is the first method of Interface5. +func (r *ImplementedGeneric[Type1]) Method2(arg1 string, arg2 testdata.Interface2, arg3 testdata.Struct5) (testdata.Interface3, error) { + panic("not implemented") // TODO: Implement +} + +` + +type ImplementedGenericMultipleParams[Type1 any, Type2 comparable] struct{} + +func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method1(arg1 string, arg2 string) (result string, err error) { + return "", nil +} + +var Interface4GenericMultipleParamsOutput = `// Method2 is the second method of Interface3. +func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method2(_ int, arg2 int) (_ int, err error) { + panic("not implemented") // TODO: Implement +} + +// Method3 is the third method of Interface3. +func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method3(arg1 bool, arg2 bool) (result1 bool, result2 bool) { + panic("not implemented") // TODO: Implement +} + +` + +var Interface5GenericMultipleParamsOutput = `// Method is the first method of Interface5. +func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method2(arg1 string, arg2 Interface2, arg3 Struct5) (Interface3, error) { + panic("not implemented") // TODO: Implement +} + +` + +// Interface6GenericMultipleParamsOutput receiver not in current package +var Interface6GenericMultipleParamsOutput = `// Method is the first method of Interface5. +func (r *ImplementedGenericMultipleParams[Type1, Type2]) Method2(arg1 string, arg2 testdata.Interface2, arg3 testdata.Struct5) (testdata.Interface3, error) { + panic("not implemented") // TODO: Implement +} + +`