Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] allow imported constraints for generics #198

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ _testmain.go

dist/
.DS_Store

**/vendor
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/matryer/moq

go 1.18
go 1.19

require (
github.com/pmezard/go-difflib v1.0.0
Expand Down
80 changes: 80 additions & 0 deletions internal/registry/generic_type_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package registry

import (
"fmt"
"go/types"
"regexp"
"strings"
)

// GenericConstraint is used as a wrapper to instantiate a new type
// for use with the registry type params
type GenericConstraint struct {
Pkg string
Path string
Name string
}

// NewGenericConstraint returns a pointer to a new GenericContstraint instance
func NewGenericConstraint(constraint string) *GenericConstraint {
return &GenericConstraint{
Pkg: getPkgName(constraint),
Path: getPackagePath(constraint),
Name: getName(constraint),
}
}

// Underlying satisfies types.Type Underlying method
func (g GenericConstraint) Underlying() types.Type {
return g
}

// String statisfies types.Type String method
func (g GenericConstraint) String() string {
return g.Name
}

var appearsImportedRegex = regexp.MustCompile(`.+\/.+\.[^\.\/]`)

// ConstraintAppearsImported checks a constraints against a regular expression
// to loosely tell if it follows an imported type pattern
func ConstraintAppearsImported(constraint string) bool {
return appearsImportedRegex.Match([]byte(constraint))
}

func getPkgName(constraint string) string {
if i := strings.LastIndexByte(constraint, '/'); i != -1 {
constraint = strings.TrimLeft(constraint[i:], "/")
}

if i := strings.LastIndexByte(constraint, '.'); i != -1 {
constraint = constraint[:i]
}

return constraint
}

func getPackagePath(constraint string) string {
if i := strings.LastIndexByte(constraint, '.'); i != -1 {
constraint = constraint[:i]
}

return strings.TrimLeft(constraint, "*")
}

func getName(constraint string) string {
var ptr bool
if constraint[0] == '*' {
ptr = true
}

if i := strings.LastIndexByte(constraint, '/'); i != -1 {
constraint = strings.TrimPrefix(constraint[i:], "/")
}

if ptr {
constraint = fmt.Sprintf("*%s", constraint)
}

return constraint
}
78 changes: 78 additions & 0 deletions internal/registry/generic_type_params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package registry

import "testing"

func TestPackageUtilities(t *testing.T) {
testCases := []struct {
Name string
Input string
ExpectedPkgName string
ExpectedPkgPath string
ExpectedTypeName string
}{
{
Name: "external package",
Input: "github.com/matryer/moq/fakepkg.MyType",
ExpectedPkgName: "fakepkg",
ExpectedPkgPath: "github.com/matryer/moq/fakepkg",
ExpectedTypeName: "fakepkg.MyType",
},
{
Name: "internal package with ptr",
Input: "*os/fs.FileInfo",
ExpectedPkgName: "fs",
ExpectedPkgPath: "os/fs",
ExpectedTypeName: "*fs.FileInfo",
},
{
Name: "internal package",
Input: "os/fs.FileInfo",
ExpectedPkgName: "fs",
ExpectedPkgPath: "os/fs",
ExpectedTypeName: "fs.FileInfo",
},
{
Name: "external package with ptr",
Input: "*github.com/matryer/moq/fakepkg.MyType",
ExpectedPkgName: "fakepkg",
ExpectedPkgPath: "github.com/matryer/moq/fakepkg",
ExpectedTypeName: "*fakepkg.MyType",
},
}

for _, test := range testCases {
t.Run(test.Name, func(t *testing.T) {
if res := getPkgName(test.Input); res != test.ExpectedPkgName {
t.Fatalf("Got unexpected package name, Expected: '%s' Got: '%s'\n", test.ExpectedPkgName, res)
}

if res := getPackagePath(test.Input); res != test.ExpectedPkgPath {
t.Fatalf("Got unexpected package path, Expected: '%s' Got: '%s'\n", test.ExpectedPkgPath, res)
}

if res := getName(test.Input); res != test.ExpectedTypeName {
t.Fatalf("Got unexpected type name, Expected: '%s' Got: '%s'\n", test.ExpectedTypeName, res)
}
})
}
}

func TestConstraintContainsPkg(t *testing.T) {
testCases := []struct {
Input string
Expected bool
}{
{Input: "github.com/matryer/moq/pkg.SomeType", Expected: true},
{Input: "*os/fs.T", Expected: true},
{Input: "os.T", Expected: false},
{Input: "os/fs", Expected: false},
{Input: "os/fs.", Expected: false},
{Input: "os/fs./os", Expected: false},
}

for _, test := range testCases {
if res := ConstraintAppearsImported(test.Input); res != test.Expected {
t.Fatalf("Got unexpected result, Expected: '%v' Got: '%v' for string: '%s'\n", test.Expected, res, test.Input)
}
}
}
17 changes: 16 additions & 1 deletion pkg/moq/moq.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func (m *Mocker) Mock(w io.Writer, namePairs ...string) error {
if data.MocksSomeMethod() {
m.registry.AddImport(types.NewPackage("sync", "sync"))
}

if m.registry.SrcPkgName() != m.mockPkgName() {
data.SrcPkgQualifier = m.registry.SrcPkgName() + "."
if !m.cfg.SkipEnsure {
Expand Down Expand Up @@ -126,9 +127,22 @@ func (m *Mocker) typeParams(tparams *types.TypeParamList) []template.TypeParamDa
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())

constraint := explicitConstraintType(typeParam)
if constraint != nil && registry.ConstraintAppearsImported(constraint.String()) {
// generate a new type
t := registry.NewGenericConstraint(constraint.String())

// since our constraint is from a package, we need to add it to the registry
m.registry.AddImport(
types.NewPackage(t.Path, t.Pkg),
)
constraint = t
}

tpd[i] = template.TypeParamData{
ParamData: template.ParamData{Var: scope.AddVar(typeParam, "")},
Constraint: explicitConstraintType(typeParam),
Constraint: constraint,
}
}

Expand All @@ -137,6 +151,7 @@ func (m *Mocker) typeParams(tparams *types.TypeParamList) []template.TypeParamDa

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++ {
Expand Down
6 changes: 6 additions & 0 deletions pkg/moq/moq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,12 @@ func TestMockGolden(t *testing.T) {
interfaces: []string{"ResetStore"},
goldenFile: filepath.Join("testpackages/withresets", "withresets_moq.golden.go"),
},
{
name: "GenericsImportedConstraint",
cfg: Config{SrcDir: "testpackages/generics_imported_constraint"},
interfaces: []string{"GenericStore1"},
goldenFile: filepath.Join("testpackages/generics_imported_constraint", "generics_imported_constraint_moq.golden.go"),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package extern

import (
"io/fs"
"os"

"github.com/matryer/moq/pkg/moq/testpackages/generics_imported_constraint/extern2"
)

// validate gh package works
type Foo1 interface {
*extern2.SomeType | *fs.PathError | os.FileMode
}

// validate with ptr works
type Foo2 interface {
*fs.PathError | os.FileMode
}

// validate without ptr works
type Foo3 interface {
fs.PathError | os.FileMode
}

type Local struct{}

// validate works with local extern, how deep can we go?
type Foo4 interface {
Local | os.File
}

// validate works with extern extern tilde pointer
type Foo5 interface {
~*extern2.SomeType | os.File
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package extern2

type SomeType string
17 changes: 17 additions & 0 deletions pkg/moq/testpackages/generics_imported_constraint/generics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package generics_imported_constraint

import (
"context"

"github.com/matryer/moq/pkg/moq/testpackages/generics_imported_constraint/extern"
)

//go:generate moq -out generics_moq_test.go -pkg generics_moq_test . GenericStore1

type GenericStore1[T extern.Foo1, J extern.Foo2, L extern.Foo3, F extern.Foo4, E extern.Foo5] interface {
Tet(ctx context.Context, handler T) error
Jet(ctx context.Context, handler J) error
Let(ctx context.Context, handler L) error
Fet(ctx context.Context, handler F) error
Eet(ctx context.Context, handler E) error
}
Loading