Skip to content

Commit

Permalink
Fix generation failure when multiple type parameters are in the receiver
Browse files Browse the repository at this point in the history
Also fixes duplicate method generation for already implemented methods
of structs with type parameters.
  • Loading branch information
ArtAndreev authored and josharian committed Apr 24, 2024
1 parent e6bec82 commit fe5f430
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 2 deletions.
50 changes: 50 additions & 0 deletions impl_test.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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",
Expand All @@ -690,13 +712,41 @@ 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",
recv: "r *Implemented",
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) {
Expand Down
17 changes: 15 additions & 2 deletions implemented.go
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
66 changes: 66 additions & 0 deletions testdata/interfaces.go
Expand Up @@ -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
}
`

0 comments on commit fe5f430

Please sign in to comment.