Repository: matryer/moq
Pull request #175 (2022-04-14 14:32:43)

feat: support generic interface generation
### Description
This addresses issue #173 regarding mock generation for interfaces with generic types. It works (almost) with the full scope of what is possible to define with golang 1.18. The test file shows the usage of interfaces with multiple type parameters, `any` constraints, constraints with embedded interface and basic type constraints, as well as aliasing of a generic interface.

### Limitation:
An Interface with a generic type that has both a type constraint and additional interface functions defined cannot be generated accurately. In order to ensure the correct interface implementation, the mock needs to be cast to the interface with a specific type parameter. Example:
```
var _ GenericInterface[string] = GenericInterfaceMock[string]
```
Consider this type parameter:
```
type Constraint interface{
   ~int32 | string
   DoSomething() error
}
``` 
It is not even guaranteed that an implementation exists, so templating cannot simply pick one. These are the options to handle this case:
1. Fail to generate such a constraint correctly
2. Remove the check against the interface in this case
3. Generate a mock for the `Constraint` interface

I don't think skipping the interface check without the user specifying it would be the right way to go and option 3 is a more involved implementation. This PR should cover the vast majority of use cases, so I chose to not implement this edge case for now and discuss it before going down that route.

cgorenflo: @matryer let me know how I can help to get this change merged!

Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..05c9a49
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,33 @@
+name: goreleaser
+
+on:
+  push:
+    tags:
+      - 'v[0-9]*'
+  workflow_dispatch:
+
+jobs:
+  goreleaser:
+    runs-on: ubuntu-latest
+    steps:
+      -
+        name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      -
+        name: Set up Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: 1.18
+
+      -
+        name: Run GoReleaser
+        uses: goreleaser/goreleaser-action@v2
+        with:
+          distribution: goreleaser
+          version: latest
+          args: release --rm-dist
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.goreleaser.yml b/.goreleaser.yml
index c67f357..93f324c 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -32,3 +32,7 @@ changelog:
     exclude:
     - '^docs:'
     - '^test:'
+release:
+  github:
+    owner: matryer
+    name: moq
diff --git a/go.mod b/go.mod
index 691efcd..4054df2 100644
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,14 @@
 module github.com/matryer/moq
 
-go 1.14
+go 1.18
 
 require (
 	github.com/pmezard/go-difflib v1.0.0
 	golang.org/x/tools v0.1.10
 )
+
+require (
+	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
+	golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+)
diff --git a/go.sum b/go.sum
index b4b556c..3d68802 100644
--- a/go.sum
+++ b/go.sum
@@ -1,31 +1,10 @@
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
 golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
 golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/registry/registry.go b/internal/registry/registry.go
index 0c923e9..ca0f1aa 100644
--- a/internal/registry/registry.go
+++ b/internal/registry/registry.go
@@ -52,17 +52,23 @@ func (r Registry) SrcPkgName() string {
 
 // LookupInterface returns the underlying interface definition of the
 // given interface name.
-func (r Registry) LookupInterface(name string) (*types.Interface, error) {
+func (r Registry) LookupInterface(name string) (*types.Interface, *types.TypeParamList, error) {
 	obj := r.SrcPkg().Scope().Lookup(name)
 	if obj == nil {
-		return nil, fmt.Errorf("interface not found: %s", name)
+		return nil, nil, fmt.Errorf("interface not found: %s", name)
 	}
 
 	if !types.IsInterface(obj.Type()) {
-		return nil, fmt.Errorf("%s (%s) is not an interface", name, obj.Type())
+		return nil, nil, fmt.Errorf("%s (%s) is not an interface", name, obj.Type())
 	}
 
-	return obj.Type().Underlying().(*types.Interface).Complete(), nil
+	var tparams *types.TypeParamList
+	named, ok := obj.Type().(*types.Named)
+	if ok {
+		tparams = named.TypeParams()
+	}
+
+	return obj.Type().Underlying().(*types.Interface).Complete(), tparams, nil
 }
 
 // MethodScope returns a new MethodScope.
diff --git a/internal/template/template.go b/internal/template/template.go
index 22bb8e8..9d7cd06 100644
--- a/internal/template/template.go
+++ b/internal/template/template.go
@@ -48,7 +48,22 @@ import (
 {{- if not $.SkipEnsure -}}
 // Ensure, that {{.MockName}} does implement {{$.SrcPkgQualifier}}{{.InterfaceName}}.
 // If this is not the case, regenerate this file with moq.
-var _ {{$.SrcPkgQualifier}}{{.InterfaceName}} = &{{.MockName}}{}
+var _ {{$.SrcPkgQualifier}}{{.InterfaceName -}} 
+	{{- if .TypeParams }}[
+		{{- range $index, $param := .TypeParams}}
+			{{- if $index}}, {{end -}}
+			{{if $param.Constraint}}{{$param.Constraint.String}}{{else}}{{$param.TypeString}}{{end}}
+		{{- end -}}
+		]
+	{{- end }} = &{{.MockName}}
+	 {{- if .TypeParams }}[
+		{{- range $index, $param := .TypeParams}}
+			{{- if $index}}, {{end -}}
+			{{if $param.Constraint}}{{$param.Constraint.String}}{{else}}{{$param.TypeString}}{{end}}
+		{{- end -}}
+		]
+	{{- end -}}
+{}
 {{- end}}
 
 // {{.MockName}} is a mock implementation of {{$.SrcPkgQualifier}}{{.InterfaceName}}.
@@ -68,7 +83,12 @@ var _ {{$.SrcPkgQualifier}}{{.InterfaceName}} = &{{.MockName}}{}
 // 		// and then make assertions.
 //
 // 	}
-type {{.MockName}} struct {
+type {{.MockName}} 
+{{- if .TypeParams -}}
+	[{{- range $index, $param := .TypeParams}}
+			{{- if $index}}, {{end}}{{$param.Name | Exported}} {{$param.TypeString}}
+	{{- end -}}]
+{{- end }} struct {
 {{- range .Methods}}
 	// {{.Name}}Func mocks the {{.Name}} method.
 	{{.Name}}Func func({{.ArgList}}) {{.ReturnArgTypeList}}
@@ -91,7 +111,13 @@ type {{.MockName}} struct {
 }
 {{range .Methods}}
 // {{.Name}} calls {{.Name}}Func.
-func (mock *{{$mock.MockName}}) {{.Name}}({{.ArgList}}) {{.ReturnArgTypeList}} {
+func (mock *{{$mock.MockName}}
+{{- if $mock.TypeParams -}}
+	[{{- range $index, $param := $mock.TypeParams}}
+		{{- if $index}}, {{end}}{{$param.Name | Exported}}
+	{{- end -}}]
+{{- end -}}	
+) {{.Name}}({{.ArgList}}) {{.ReturnArgTypeList}} {
 {{- if not $.StubImpl}}
 	if mock.{{.Name}}Func == nil {
 		panic("{{$mock.MockName}}.{{.Name}}Func: method is nil but {{$mock.InterfaceName}}.{{.Name}} was just called")
@@ -134,7 +160,13 @@ func (mock *{{$mock.MockName}}) {{.Name}}({{.ArgList}}) {{.ReturnArgTypeList}} {
 // {{.Name}}Calls gets all the calls that were made to {{.Name}}.
 // Check the length with:
 //     len(mocked{{$mock.InterfaceName}}.{{.Name}}Calls())
-func (mock *{{$mock.MockName}}) {{.Name}}Calls() []struct {
+func (mock *{{$mock.MockName}}
+{{- if $mock.TypeParams -}}
+	[{{- range $index, $param := $mock.TypeParams}}
+		{{- if $index}}, {{end}}{{$param.Name | Exported}}
+	{{- end -}}]
+{{- end -}}	
+) {{.Name}}Calls() []struct {
 		{{- range .Params}}
 		{{.Name | Exported}} {{.TypeString}}
 		{{- end}}
diff --git a/internal/template/template_data.go b/internal/template/template_data.go
index 0a95bb5..12c9117 100644
--- a/internal/template/template_data.go
+++ b/internal/template/template_data.go
@@ -2,6 +2,7 @@ package template
 
 import (
 	"fmt"
+	"go/types"
 	"strings"
 
 	"github.com/matryer/moq/internal/registry"
@@ -33,6 +34,7 @@ func (d Data) MocksSomeMethod() bool {
 type MockData struct {
 	InterfaceName string
 	MockName      string
+	TypeParams    []TypeParamData
 	Methods       []MethodData
 }
 
@@ -87,6 +89,11 @@ func (m MethodData) ReturnArgNameList() string {
 	return strings.Join(params, ", ")
 }
 
+type TypeParamData struct {
+	ParamData
+	Constraint types.Type
+}
+
 // ParamData is the data which represents a parameter to some method of
 // an interface.
 type ParamData struct {
diff --git a/pkg/moq/moq.go b/pkg/moq/moq.go
index 3029a0c..a33a4cb 100644
--- a/pkg/moq/moq.go
+++ b/pkg/moq/moq.go
@@ -3,6 +3,7 @@ package moq
 import (
 	"bytes"
 	"errors"
+	"go/token"
 	"go/types"
 	"io"
 	"strings"
@@ -57,7 +58,7 @@ func (m *Mocker) Mock(w io.Writer, namePairs ...string) error {
 	mocks := make([]template.MockData, len(namePairs))
 	for i, np := range namePairs {
 		name, mockName := parseInterfaceName(np)
-		iface, err := m.registry.LookupInterface(name)
+		iface, tparams, err := m.registry.LookupInterface(name)
 		if err != nil {
 			return err
 		}
@@ -71,6 +72,7 @@ func (m *Mocker) Mock(w io.Writer, namePairs ...string) error {
 			InterfaceName: name,
 			MockName:      mockName,
 			Methods:       methods,
+			TypeParams:    m.typeParams(tparams),
 		}
 	}
 
@@ -110,6 +112,43 @@ func (m *Mocker) Mock(w io.Writer, namePairs ...string) error {
 	return nil
 }
 
+func (m *Mocker) typeParams(tparams *types.TypeParamList) []template.TypeParamData {
+	var tpd []template.TypeParamData
+	if tparams == nil {
+		return tpd
+	}
+
+	tpd = make([]template.TypeParamData, tparams.Len())
+
+	scope := m.registry.MethodScope()
+	for i := 0; i < len(tpd); i++ {
+		tp := tparams.At(i)
+		typeParam := types.NewParam(token.Pos(i), tp.Obj().Pkg(), tp.Obj().Name(), tp.Constraint())
+		tpd[i] = template.TypeParamData{
+			ParamData:  template.ParamData{Var: scope.AddVar(typeParam, "")},
+			Constraint: explicitConstraintType(typeParam),
+		}
+	}
+
+	return tpd
+}
+
+func explicitConstraintType(typeParam *types.Var) (t types.Type) {
+	underlying := typeParam.Type().Underlying().(*types.Interface)
+	// check if any of the embedded types is either a basic type or a union,
+	// because the generic type has to be an alias for one of those types then
+	for j := 0; j < underlying.NumEmbeddeds(); j++ {
+		t := underlying.EmbeddedType(j)
+		switch t := t.(type) {
+		case *types.Basic:
+			return t
+		case *types.Union: // only unions of basic types are allowed, so just take the first one as a valid type constraint
+			return t.Term(0).Type()
+		}
+	}
+	return nil
+}
+
 func (m *Mocker) methodData(f *types.Func) template.MethodData {
 	sig := f.Type().(*types.Signature)
 


Test patch:
diff --git a/pkg/moq/moq_test.go b/pkg/moq/moq_test.go
index 8dbe3db..ccd5006 100644
--- a/pkg/moq/moq_test.go
+++ b/pkg/moq/moq_test.go
@@ -383,6 +383,12 @@ func TestMockGolden(t *testing.T) {
 			interfaces: []string{"ShadowTypes"},
 			goldenFile: filepath.Join("testpackages/shadowtypes", "shadowtypes_moq.golden.go"),
 		},
+		{
+			name:       "Generics",
+			cfg:        Config{SrcDir: "testpackages/generics"},
+			interfaces: []string{"GenericStore1", "GenericStore2", "AliasStore"},
+			goldenFile: filepath.Join("testpackages/generics", "generics_moq.golden.go"),
+		},
 	}
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
diff --git a/pkg/moq/testpackages/generics/generics.go b/pkg/moq/testpackages/generics/generics.go
new file mode 100644
index 0000000..9e7a82c
--- /dev/null
+++ b/pkg/moq/testpackages/generics/generics.go
@@ -0,0 +1,32 @@
+package generics
+
+import (
+	"context"
+	"fmt"
+)
+
+type GenericStore1[T Key1, S any] interface {
+	Get(ctx context.Context, id T) (S, error)
+	Create(ctx context.Context, id T, value S) error
+}
+
+type GenericStore2[T Key2, S any] interface {
+	Get(ctx context.Context, id T) (S, error)
+	Create(ctx context.Context, id T, value S) error
+}
+
+type AliasStore GenericStore1[KeyImpl, bool]
+
+type Key1 interface {
+	fmt.Stringer
+}
+
+type Key2 interface {
+	~[]byte | string
+}
+
+type KeyImpl []byte
+
+func (x KeyImpl) String() string {
+	return string(x)
+}
diff --git a/pkg/moq/testpackages/generics/generics_moq.golden.go b/pkg/moq/testpackages/generics/generics_moq.golden.go
new file mode 100644
index 0000000..2c437fb
--- /dev/null
+++ b/pkg/moq/testpackages/generics/generics_moq.golden.go
@@ -0,0 +1,387 @@
+// Code generated by moq; DO NOT EDIT.
+// github.com/matryer/moq
+
+package generics
+
+import (
+	"context"
+	"sync"
+)
+
+// Ensure, that GenericStore1Mock does implement GenericStore1.
+// If this is not the case, regenerate this file with moq.
+var _ GenericStore1[Key1, any] = &GenericStore1Mock[Key1, any]{}
+
+// GenericStore1Mock is a mock implementation of GenericStore1.
+//
+// 	func TestSomethingThatUsesGenericStore1(t *testing.T) {
+//
+// 		// make and configure a mocked GenericStore1
+// 		mockedGenericStore1 := &GenericStore1Mock{
+// 			CreateFunc: func(ctx context.Context, id T, value S) error {
+// 				panic("mock out the Create method")
+// 			},
+// 			GetFunc: func(ctx context.Context, id T) (S, error) {
+// 				panic("mock out the Get method")
+// 			},
+// 		}
+//
+// 		// use mockedGenericStore1 in code that requires GenericStore1
+// 		// and then make assertions.
+//
+// 	}
+type GenericStore1Mock[T Key1, S any] struct {
+	// CreateFunc mocks the Create method.
+	CreateFunc func(ctx context.Context, id T, value S) error
+
+	// GetFunc mocks the Get method.
+	GetFunc func(ctx context.Context, id T) (S, error)
+
+	// calls tracks calls to the methods.
+	calls struct {
+		// Create holds details about calls to the Create method.
+		Create []struct {
+			// Ctx is the ctx argument value.
+			Ctx context.Context
+			// ID is the id argument value.
+			ID T
+			// Value is the value argument value.
+			Value S
+		}
+		// Get holds details about calls to the Get method.
+		Get []struct {
+			// Ctx is the ctx argument value.
+			Ctx context.Context
+			// ID is the id argument value.
+			ID T
+		}
+	}
+	lockCreate sync.RWMutex
+	lockGet    sync.RWMutex
+}
+
+// Create calls CreateFunc.
+func (mock *GenericStore1Mock[T, S]) Create(ctx context.Context, id T, value S) error {
+	if mock.CreateFunc == nil {
+		panic("GenericStore1Mock.CreateFunc: method is nil but GenericStore1.Create was just called")
+	}
+	callInfo := struct {
+		Ctx   context.Context
+		ID    T
+		Value S
+	}{
+		Ctx:   ctx,
+		ID:    id,
+		Value: value,
+	}
+	mock.lockCreate.Lock()
+	mock.calls.Create = append(mock.calls.Create, callInfo)
+	mock.lockCreate.Unlock()
+	return mock.CreateFunc(ctx, id, value)
+}
+
+// CreateCalls gets all the calls that were made to Create.
+// Check the length with:
+//     len(mockedGenericStore1.CreateCalls())
+func (mock *GenericStore1Mock[T, S]) CreateCalls() []struct {
+	Ctx   context.Context
+	ID    T
+	Value S
+} {
+	var calls []struct {
+		Ctx   context.Context
+		ID    T
+		Value S
+	}
+	mock.lockCreate.RLock()
+	calls = mock.calls.Create
+	mock.lockCreate.RUnlock()
+	return calls
+}
+
+// Get calls GetFunc.
+func (mock *GenericStore1Mock[T, S]) Get(ctx context.Context, id T) (S, error) {
+	if mock.GetFunc == nil {
+		panic("GenericStore1Mock.GetFunc: method is nil but GenericStore1.Get was just called")
+	}
+	callInfo := struct {
+		Ctx context.Context
+		ID  T
+	}{
+		Ctx: ctx,
+		ID:  id,
+	}
+	mock.lockGet.Lock()
+	mock.calls.Get = append(mock.calls.Get, callInfo)
+	mock.lockGet.Unlock()
+	return mock.GetFunc(ctx, id)
+}
+
+// GetCalls gets all the calls that were made to Get.
+// Check the length with:
+//     len(mockedGenericStore1.GetCalls())
+func (mock *GenericStore1Mock[T, S]) GetCalls() []struct {
+	Ctx context.Context
+	ID  T
+} {
+	var calls []struct {
+		Ctx context.Context
+		ID  T
+	}
+	mock.lockGet.RLock()
+	calls = mock.calls.Get
+	mock.lockGet.RUnlock()
+	return calls
+}
+
+// Ensure, that GenericStore2Mock does implement GenericStore2.
+// If this is not the case, regenerate this file with moq.
+var _ GenericStore2[[]byte, any] = &GenericStore2Mock[[]byte, any]{}
+
+// GenericStore2Mock is a mock implementation of GenericStore2.
+//
+// 	func TestSomethingThatUsesGenericStore2(t *testing.T) {
+//
+// 		// make and configure a mocked GenericStore2
+// 		mockedGenericStore2 := &GenericStore2Mock{
+// 			CreateFunc: func(ctx context.Context, id T, value S) error {
+// 				panic("mock out the Create method")
+// 			},
+// 			GetFunc: func(ctx context.Context, id T) (S, error) {
+// 				panic("mock out the Get method")
+// 			},
+// 		}
+//
+// 		// use mockedGenericStore2 in code that requires GenericStore2
+// 		// and then make assertions.
+//
+// 	}
+type GenericStore2Mock[T Key2, S any] struct {
+	// CreateFunc mocks the Create method.
+	CreateFunc func(ctx context.Context, id T, value S) error
+
+	// GetFunc mocks the Get method.
+	GetFunc func(ctx context.Context, id T) (S, error)
+
+	// calls tracks calls to the methods.
+	calls struct {
+		// Create holds details about calls to the Create method.
+		Create []struct {
+			// Ctx is the ctx argument value.
+			Ctx context.Context
+			// ID is the id argument value.
+			ID T
+			// Value is the value argument value.
+			Value S
+		}
+		// Get holds details about calls to the Get method.
+		Get []struct {
+			// Ctx is the ctx argument value.
+			Ctx context.Context
+			// ID is the id argument value.
+			ID T
+		}
+	}
+	lockCreate sync.RWMutex
+	lockGet    sync.RWMutex
+}
+
+// Create calls CreateFunc.
+func (mock *GenericStore2Mock[T, S]) Create(ctx context.Context, id T, value S) error {
+	if mock.CreateFunc == nil {
+		panic("GenericStore2Mock.CreateFunc: method is nil but GenericStore2.Create was just called")
+	}
+	callInfo := struct {
+		Ctx   context.Context
+		ID    T
+		Value S
+	}{
+		Ctx:   ctx,
+		ID:    id,
+		Value: value,
+	}
+	mock.lockCreate.Lock()
+	mock.calls.Create = append(mock.calls.Create, callInfo)
+	mock.lockCreate.Unlock()
+	return mock.CreateFunc(ctx, id, value)
+}
+
+// CreateCalls gets all the calls that were made to Create.
+// Check the length with:
+//     len(mockedGenericStore2.CreateCalls())
+func (mock *GenericStore2Mock[T, S]) CreateCalls() []struct {
+	Ctx   context.Context
+	ID    T
+	Value S
+} {
+	var calls []struct {
+		Ctx   context.Context
+		ID    T
+		Value S
+	}
+	mock.lockCreate.RLock()
+	calls = mock.calls.Create
+	mock.lockCreate.RUnlock()
+	return calls
+}
+
+// Get calls GetFunc.
+func (mock *GenericStore2Mock[T, S]) Get(ctx context.Context, id T) (S, error) {
+	if mock.GetFunc == nil {
+		panic("GenericStore2Mock.GetFunc: method is nil but GenericStore2.Get was just called")
+	}
+	callInfo := struct {
+		Ctx context.Context
+		ID  T
+	}{
+		Ctx: ctx,
+		ID:  id,
+	}
+	mock.lockGet.Lock()
+	mock.calls.Get = append(mock.calls.Get, callInfo)
+	mock.lockGet.Unlock()
+	return mock.GetFunc(ctx, id)
+}
+
+// GetCalls gets all the calls that were made to Get.
+// Check the length with:
+//     len(mockedGenericStore2.GetCalls())
+func (mock *GenericStore2Mock[T, S]) GetCalls() []struct {
+	Ctx context.Context
+	ID  T
+} {
+	var calls []struct {
+		Ctx context.Context
+		ID  T
+	}
+	mock.lockGet.RLock()
+	calls = mock.calls.Get
+	mock.lockGet.RUnlock()
+	return calls
+}
+
+// Ensure, that AliasStoreMock does implement AliasStore.
+// If this is not the case, regenerate this file with moq.
+var _ AliasStore = &AliasStoreMock{}
+
+// AliasStoreMock is a mock implementation of AliasStore.
+//
+// 	func TestSomethingThatUsesAliasStore(t *testing.T) {
+//
+// 		// make and configure a mocked AliasStore
+// 		mockedAliasStore := &AliasStoreMock{
+// 			CreateFunc: func(ctx context.Context, id KeyImpl, value bool) error {
+// 				panic("mock out the Create method")
+// 			},
+// 			GetFunc: func(ctx context.Context, id KeyImpl) (bool, error) {
+// 				panic("mock out the Get method")
+// 			},
+// 		}
+//
+// 		// use mockedAliasStore in code that requires AliasStore
+// 		// and then make assertions.
+//
+// 	}
+type AliasStoreMock struct {
+	// CreateFunc mocks the Create method.
+	CreateFunc func(ctx context.Context, id KeyImpl, value bool) error
+
+	// GetFunc mocks the Get method.
+	GetFunc func(ctx context.Context, id KeyImpl) (bool, error)
+
+	// calls tracks calls to the methods.
+	calls struct {
+		// Create holds details about calls to the Create method.
+		Create []struct {
+			// Ctx is the ctx argument value.
+			Ctx context.Context
+			// ID is the id argument value.
+			ID KeyImpl
+			// Value is the value argument value.
+			Value bool
+		}
+		// Get holds details about calls to the Get method.
+		Get []struct {
+			// Ctx is the ctx argument value.
+			Ctx context.Context
+			// ID is the id argument value.
+			ID KeyImpl
+		}
+	}
+	lockCreate sync.RWMutex
+	lockGet    sync.RWMutex
+}
+
+// Create calls CreateFunc.
+func (mock *AliasStoreMock) Create(ctx context.Context, id KeyImpl, value bool) error {
+	if mock.CreateFunc == nil {
+		panic("AliasStoreMock.CreateFunc: method is nil but AliasStore.Create was just called")
+	}
+	callInfo := struct {
+		Ctx   context.Context
+		ID    KeyImpl
+		Value bool
+	}{
+		Ctx:   ctx,
+		ID:    id,
+		Value: value,
+	}
+	mock.lockCreate.Lock()
+	mock.calls.Create = append(mock.calls.Create, callInfo)
+	mock.lockCreate.Unlock()
+	return mock.CreateFunc(ctx, id, value)
+}
+
+// CreateCalls gets all the calls that were made to Create.
+// Check the length with:
+//     len(mockedAliasStore.CreateCalls())
+func (mock *AliasStoreMock) CreateCalls() []struct {
+	Ctx   context.Context
+	ID    KeyImpl
+	Value bool
+} {
+	var calls []struct {
+		Ctx   context.Context
+		ID    KeyImpl
+		Value bool
+	}
+	mock.lockCreate.RLock()
+	calls = mock.calls.Create
+	mock.lockCreate.RUnlock()
+	return calls
+}
+
+// Get calls GetFunc.
+func (mock *AliasStoreMock) Get(ctx context.Context, id KeyImpl) (bool, error) {
+	if mock.GetFunc == nil {
+		panic("AliasStoreMock.GetFunc: method is nil but AliasStore.Get was just called")
+	}
+	callInfo := struct {
+		Ctx context.Context
+		ID  KeyImpl
+	}{
+		Ctx: ctx,
+		ID:  id,
+	}
+	mock.lockGet.Lock()
+	mock.calls.Get = append(mock.calls.Get, callInfo)
+	mock.lockGet.Unlock()
+	return mock.GetFunc(ctx, id)
+}
+
+// GetCalls gets all the calls that were made to Get.
+// Check the length with:
+//     len(mockedAliasStore.GetCalls())
+func (mock *AliasStoreMock) GetCalls() []struct {
+	Ctx context.Context
+	ID  KeyImpl
+} {
+	var calls []struct {
+		Ctx context.Context
+		ID  KeyImpl
+	}
+	mock.lockGet.RLock()
+	calls = mock.calls.Get
+	mock.lockGet.RUnlock()
+	return calls
+}
diff --git a/pkg/moq/testpackages/go.mod b/pkg/moq/testpackages/go.mod
index 2755150..b65ffdf 100644
--- a/pkg/moq/testpackages/go.mod
+++ b/pkg/moq/testpackages/go.mod
@@ -1,5 +1,5 @@
 module github.com/matryer/moq/pkg/moq/testpackages
 
-go 1.14
+go 1.18
 
 require github.com/sudo-suhas/moq-test-pkgs/somerepo v0.0.0-20200816045313-d2f573eea6c7