Repository: matryer/moq (go)

Issue:
Moq ignores package names of arguments of generic types
When mocking a method with a generic type, moq ignores package names of type arguments.

This works:
```go
package example

//go:generate moq -skip-ensure -stub -rm -out mocks.go . Foo:FooMock

type Bar[T any] struct{}

type Foo interface {
	Foo(bar Bar[Type])
}
```

But this does not:
```go
package example

//go:generate moq -skip-ensure -stub -rm -out mocks.go . Foo:FooMock

import "path/to/pkg"

type Bar[T any] struct{}

type Foo interface {
	Foo(bar Bar[pkg.Type])
}
```

The generated code instantiates `Bar` as `Bar[Type]` instead of `Bar[pkg.Type]`, failing to compile:

```go
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq

package example

import (
	"sync"
)

type FooMock struct {
	// FooFunc mocks the Foo method.
	FooFunc func(bar Bar[Type])

	// calls tracks calls to the methods.
	calls struct {
		// Foo holds details about calls to the Foo method.
		Foo []struct {
			// Bar is the bar argument value.
			Bar Bar[Type]
		}
	}
	lockFoo sync.RWMutex
}
```

I've tried workarounds like dot imports and type aliases, but got the same results. If `Bar` type is a built-in like `map`, it works fine.
moq version `v0.2.7`.

Pull request description:
Fix: add imports for Named Type its Instance Types
Fixes missing imports and package prefixing to generic types. See #177 

In the following example a method must be mocked which has a return parameter that has a generic type: `otherpackage.Foo`. 

```go
package genericreturn

import "github.com/matryer/moq/pkg/moq/testpackages/genericreturn/otherpackage"

type GenericBar[T any] struct {
	Bar T
}

type IFooBar interface {
	Foobar() GenericBar[otherpackage.Foo]
}
```

```go
package otherpackage

type Foo struct {
	A int
	B string
}
```

Currently mocking `IFooBar` will result in a missing `otherpackage` import and the package prefix to `Foo`. Note: `FoobarFunc: func() GenericBar[Foo]` instead of `FoobarFunc: func() GenericBar[otherpackage.Foo]`.
```
// IFooBarMock is a mock implementation of IFooBar.
//
//	func TestSomethingThatUsesIFooBar(t *testing.T) {
//
//		// make and configure a mocked IFooBar
//		mockedIFooBar := &IFooBarMock{
//			FoobarFunc: func() GenericBar[Foo] {
//				panic("mock out the Foobar method")
//			},
//		}
//
//		// use mockedIFooBar in code that requires IFooBar
//		// and then make assertions.
//
//	}
```

This happens because of an invalid key lookup here, which returns an empty string `""`: [internal/registry/var.go](https://github.com/matryer/moq/blob/373b8f9e00b57d70c32d45f8d765836505a1e527/internal/registry/var.go#L38)

This is fixed by checking if a Named Type has type arguments, and populating imports for those types.
[internal/registry/method_scope.go](https://github.com/matryer/moq/blob/974b193b71073e82986638ac716096806f36b665/internal/registry/method_scope.go#L85-L91)
```go
func (m MethodScope) populateImports(t types.Type, imports map[string]*Package) {
	switch t := t.(type) {
	case *types.Named:
		...
		// The imports of a Type with a TypeList must be added to the imports list
		// For example: Foo[otherpackage.Bar] , must have otherpackage imported
		if targs := t.TypeArgs(); targs != nil {
			for i := 0; i < targs.Len(); i++ {
				m.populateImports(targs.At(i), imports)
			}
		}
```

A new golden test was added for this specific case.

Closes #177
Closes #190

Patch:
diff --git a/internal/registry/method_scope.go b/internal/registry/method_scope.go
index cf6ca25..65b5616 100644
--- a/internal/registry/method_scope.go
+++ b/internal/registry/method_scope.go
@@ -82,6 +82,13 @@ func (m MethodScope) populateImports(t types.Type, imports map[string]*Package)
 		if pkg := t.Obj().Pkg(); pkg != nil {
 			imports[stripVendorPath(pkg.Path())] = m.registry.AddImport(pkg)
 		}
+		// The imports of a Type with a TypeList must be added to the imports list
+		// For example: Foo[otherpackage.Bar] , must have otherpackage imported
+		if targs := t.TypeArgs(); targs != nil {
+			for i := 0; i < targs.Len(); i++ {
+				m.populateImports(targs.At(i), imports)
+			}
+		}
 
 	case *types.Array:
 		m.populateImports(t.Elem(), imports)


Test patch:
diff --git a/pkg/moq/moq_test.go b/pkg/moq/moq_test.go
index 7dceb8f..2813238 100644
--- a/pkg/moq/moq_test.go
+++ b/pkg/moq/moq_test.go
@@ -389,6 +389,12 @@ func TestMockGolden(t *testing.T) {
 			interfaces: []string{"GenericStore1", "GenericStore2", "AliasStore"},
 			goldenFile: filepath.Join("testpackages/generics", "generics_moq.golden.go"),
 		},
+		{
+			name:       "Generic Return Argument Type",
+			cfg:        Config{SrcDir: "testpackages/genericreturn"},
+			interfaces: []string{"IFooBar"},
+			goldenFile: filepath.Join("testpackages/genericreturn", "genericreturn.golden.go"),
+		},
 		{
 			name:       "TransientImport",
 			cfg:        Config{SrcDir: "testpackages/transientimport"},
@@ -659,7 +665,8 @@ func TestParseError(t *testing.T) {
 		t.Errorf("expected error but got nil")
 		return
 	}
-	if !strings.Contains(err.Error(), `could not import github.com/matryer/notexist (invalid package name: "")`) {
+	// The first clause is the common error. Only in go version 1.19.10 the error differs
+	if !strings.Contains(err.Error(), `could not import github.com/matryer/notexist (invalid package name: "")`) && !strings.Contains(err.Error(), "stderr: go build github.com/matryer/notexist") {
 		t.Errorf("unexpected error: %s", err.Error())
 	}
 }
diff --git a/pkg/moq/testpackages/genericreturn/genericreturn.go b/pkg/moq/testpackages/genericreturn/genericreturn.go
new file mode 100644
index 0000000..da23042
--- /dev/null
+++ b/pkg/moq/testpackages/genericreturn/genericreturn.go
@@ -0,0 +1,11 @@
+package genericreturn
+
+import "github.com/matryer/moq/pkg/moq/testpackages/genericreturn/otherpackage"
+
+type GenericBar[T any] struct {
+	Bar T
+}
+
+type IFooBar interface {
+	Foobar() GenericBar[otherpackage.Foo]
+}
diff --git a/pkg/moq/testpackages/genericreturn/genericreturn.golden.go b/pkg/moq/testpackages/genericreturn/genericreturn.golden.go
new file mode 100644
index 0000000..38d27c8
--- /dev/null
+++ b/pkg/moq/testpackages/genericreturn/genericreturn.golden.go
@@ -0,0 +1,68 @@
+// Code generated by moq; DO NOT EDIT.
+// github.com/matryer/moq
+
+package genericreturn
+
+import (
+	"github.com/matryer/moq/pkg/moq/testpackages/genericreturn/otherpackage"
+	"sync"
+)
+
+// Ensure, that IFooBarMock does implement IFooBar.
+// If this is not the case, regenerate this file with moq.
+var _ IFooBar = &IFooBarMock{}
+
+// IFooBarMock is a mock implementation of IFooBar.
+//
+//	func TestSomethingThatUsesIFooBar(t *testing.T) {
+//
+//		// make and configure a mocked IFooBar
+//		mockedIFooBar := &IFooBarMock{
+//			FoobarFunc: func() GenericBar[otherpackage.Foo] {
+//				panic("mock out the Foobar method")
+//			},
+//		}
+//
+//		// use mockedIFooBar in code that requires IFooBar
+//		// and then make assertions.
+//
+//	}
+type IFooBarMock struct {
+	// FoobarFunc mocks the Foobar method.
+	FoobarFunc func() GenericBar[otherpackage.Foo]
+
+	// calls tracks calls to the methods.
+	calls struct {
+		// Foobar holds details about calls to the Foobar method.
+		Foobar []struct {
+		}
+	}
+	lockFoobar sync.RWMutex
+}
+
+// Foobar calls FoobarFunc.
+func (mock *IFooBarMock) Foobar() GenericBar[otherpackage.Foo] {
+	if mock.FoobarFunc == nil {
+		panic("IFooBarMock.FoobarFunc: method is nil but IFooBar.Foobar was just called")
+	}
+	callInfo := struct {
+	}{}
+	mock.lockFoobar.Lock()
+	mock.calls.Foobar = append(mock.calls.Foobar, callInfo)
+	mock.lockFoobar.Unlock()
+	return mock.FoobarFunc()
+}
+
+// FoobarCalls gets all the calls that were made to Foobar.
+// Check the length with:
+//
+//	len(mockedIFooBar.FoobarCalls())
+func (mock *IFooBarMock) FoobarCalls() []struct {
+} {
+	var calls []struct {
+	}
+	mock.lockFoobar.RLock()
+	calls = mock.calls.Foobar
+	mock.lockFoobar.RUnlock()
+	return calls
+}
diff --git a/pkg/moq/testpackages/genericreturn/otherpackage/otherpackage.go b/pkg/moq/testpackages/genericreturn/otherpackage/otherpackage.go
new file mode 100644
index 0000000..e755f52
--- /dev/null
+++ b/pkg/moq/testpackages/genericreturn/otherpackage/otherpackage.go
@@ -0,0 +1,6 @@
+package otherpackage
+
+type Foo struct {
+	A int
+	B string
+}