Skip to content
Open
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
7 changes: 7 additions & 0 deletions mockgen/internal/tests/embedded_alias/aliases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package embedded_alias

type Getter[T any] interface {
Get() T
}

type LocalAlias = Getter[int]
10 changes: 10 additions & 0 deletions mockgen/internal/tests/embedded_alias/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package embedded_alias

import "go.uber.org/mock/mockgen/internal/tests/embedded_alias/other"

//go:generate mockgen -source=input.go -destination=mock_test.go -package=embedded_alias

type Example interface {
LocalAlias
other.Alias
}
22 changes: 22 additions & 0 deletions mockgen/internal/tests/embedded_alias/input_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package embedded_alias

import (
"testing"

"go.uber.org/mock/gomock"
)

var _ Example = (*MockExample)(nil)

func TestEmbeddedAliases(t *testing.T) {
m := NewMockExample(gomock.NewController(t))
m.EXPECT().Get().Return(42)
m.EXPECT().Map("key").Return(true)

if got := m.Get(); got != 42 {
t.Errorf("Get() = %d, want 42", got)
}
if !m.Map("key") {
t.Error("Map() = false, want true")
}
}
68 changes: 68 additions & 0 deletions mockgen/internal/tests/embedded_alias/mock_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions mockgen/internal/tests/embedded_alias/other/aliases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package other

type Mapper[K, V any] interface {
Map(K) V
}

type Alias = Mapper[string, bool]
16 changes: 16 additions & 0 deletions mockgen/internal/tests/embedded_generic_callback/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package mypackage

//go:generate mockgen -destination=mock_test.go -package=mypackage go.uber.org/mock/mockgen/internal/tests/embedded_generic_callback MyInterface

// Regression case from https://github.com/uber-go/mock/issues/271#issuecomment-3442087301.
type Callback[T any] func(item T)

type Base[T any] interface {
OnEvent(callback Callback[T])
}

type MyStruct struct{}

type MyInterface interface {
Base[MyStruct]
}
24 changes: 24 additions & 0 deletions mockgen/internal/tests/embedded_generic_callback/input_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package mypackage

import (
"testing"

"go.uber.org/mock/gomock"
)

func TestEmbeddedGenericCallback(t *testing.T) {
m := NewMockMyInterface(gomock.NewController(t))
m.EXPECT().OnEvent(gomock.Any()).Do(func(callback Callback[MyStruct]) {
callback(MyStruct{})
})

// The generated method must accept Callback[MyStruct] from this package.
var target MyInterface = m
called := false
target.OnEvent(func(MyStruct) {
called = true
})
if !called {
t.Fatal("OnEvent did not invoke the callback")
}
}
52 changes: 52 additions & 0 deletions mockgen/internal/tests/embedded_generic_callback/mock_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 35 additions & 6 deletions mockgen/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (p *fileParser) parseAuxFiles(auxFiles string) error {
}

func (p *fileParser) addAuxInterfacesFromFile(pkg string, file *ast.File) {
for ni := range iterInterfaces(file) {
for ni := range iterInterfaces(file, true) {
p.auxInterfaces.Set(pkg, ni.name.Name, ni)
}
}
Expand All @@ -242,7 +242,7 @@ func (p *fileParser) parseFile(importPath string, file *ast.File) (*model.Packag
}

var is []*model.Interface
for ni := range iterInterfaces(file) {
for ni := range iterInterfaces(file, false) {
name := ni.name.String()

if _, ok := p.excludeNamesSet[name]; ok {
Expand Down Expand Up @@ -296,7 +296,7 @@ func (p *fileParser) parsePackage(path string) (*fileParser, error) {

for _, pkg := range pkgs {
file := ast.MergePackageFiles(pkg, ast.FilterFuncDuplicates|ast.FilterUnassociatedComments|ast.FilterImportDuplicates)
for ni := range iterInterfaces(file) {
for ni := range iterInterfaces(file, true) {
newP.importedInterfaces.Set(path, ni.name.Name, ni)
}
imports, _ := importsOfFile(file)
Expand Down Expand Up @@ -355,6 +355,12 @@ func (p *fileParser) constructTps(it *namedInterface) (tps map[string]model.Type
// parseInterface loads interface specified by pkg and name, parses it and returns
// a new model with the parsed.
func (p *fileParser) parseInterface(name, pkg string, it *namedInterface) (*model.Interface, error) {
if it.inProgress {
return nil, p.errorf(it.name.Pos(), "cyclic interface embedding involving %s", name)
}
it.inProgress = true
defer func() { it.inProgress = false }()

iface := &model.Interface{Name: name}
tps := p.constructTps(it)
tp, err := p.parseFieldList(pkg, it.typeParams, tps)
Expand Down Expand Up @@ -766,10 +772,13 @@ type namedInterface struct {
typeParams []*ast.Field
embeddedInstTypeParams []ast.Expr
instTypes []model.Type
inProgress bool
}

// Create an iterator over all interfaces in file.
func iterInterfaces(file *ast.File) <-chan *namedInterface {
// iterInterfaces returns interface declarations. When includeAliases is true,
// aliases are also available for resolving embedded interfaces, but should not
// be used to discover additional interfaces to mock.
func iterInterfaces(file *ast.File, includeAliases bool) <-chan *namedInterface {
ch := make(chan *namedInterface)
go func() {
for _, decl := range file.Decls {
Expand All @@ -784,7 +793,27 @@ func iterInterfaces(file *ast.File) <-chan *namedInterface {
}
it, ok := ts.Type.(*ast.InterfaceType)
if !ok {
continue
if !includeAliases || !ts.Assign.IsValid() {
continue
}
typ := ts.Type
for {
paren, ok := typ.(*ast.ParenExpr)
if !ok {
break
}
typ = paren.X
}
switch typ.(type) {
case *ast.Ident, *ast.SelectorExpr, *ast.IndexExpr, *ast.IndexListExpr:
// An alias has the same methods as its target. Resolve it
// using the existing embedded-interface parsing logic.
it = &ast.InterfaceType{Methods: &ast.FieldList{
List: []*ast.Field{{Type: typ}},
}}
default:
continue
}
}

ch <- &namedInterface{name: ts.Name, it: it, typeParams: getTypeSpecTypeParams(ts)}
Expand Down
Loading