Skip to content

Fix deepcopy for generics #274

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

Merged
merged 5 commits into from
Aug 26, 2024
Merged
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
1 change: 1 addition & 0 deletions v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ require (

require (
github.com/go-logr/logr v0.2.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is optional but I figured it'd be more useful to use cmp.Diff instead of a two complex structs that are quite hard to read in the tests. k/k already has this dependency https://github.com/kubernetes/kubernetes/blob/master/go.mod#L38 so we are not adding anything new for them.

golang.org/x/mod v0.14.0 // indirect
)
2 changes: 2 additions & 0 deletions v2/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
Expand Down
9 changes: 8 additions & 1 deletion v2/parser/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,9 @@ func goVarNameToName(in string) types.Name {
return goNameToName(nameParts[1])
}

// goNameToName converts a go name string to a gengo types.Name.
// It operates solely on the string on a best effort basis. The name may be updated
// in walkType for generics.
func goNameToName(in string) types.Name {
// Detect anonymous type names. (These may have '.' characters because
// embedded types may have packages, so we detect them specially.)
Expand Down Expand Up @@ -602,6 +605,10 @@ func goNameToName(in string) types.Name {
// The final "." is the name of the type--previous ones must
// have been in the package path.
name.Package, name.Name = strings.Join(nameParts[:n-1], "."), nameParts[n-1]
// Add back the generic component now that the package and type name have been separated.
if genericIndex != len(in) {
name.Name = name.Name + in[genericIndex:]
}
}
return name
}
Expand Down Expand Up @@ -745,7 +752,7 @@ func (p *Parser) walkType(u types.Universe, useName *types.Name, in gotypes.Type
}
out.Kind = types.Alias
out.Underlying = p.walkType(u, nil, t.Underlying())
case *gotypes.Struct:
case *gotypes.Struct, *gotypes.Interface:
name := goNameToName(t.String())
tpMap := map[string]*types.Type{}
if t.TypeParams().Len() != 0 {
Expand Down
91 changes: 89 additions & 2 deletions v2/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"sort"
"testing"

"github.com/google/go-cmp/cmp"
"golang.org/x/tools/go/packages"
"k8s.io/gengo/v2/types"
)
Expand Down Expand Up @@ -893,6 +894,62 @@ func TestStructParse(t *testing.T) {
}
},
},
{
description: "generic on field",
testFile: "./testdata/generic-field",
expected: func() *types.Type {
fieldType := &types.Type{
Name: types.Name{
Package: "k8s.io/gengo/v2/parser/testdata/generic-field",
Name: "Blah[T]",
},
Kind: types.Struct,
CommentLines: []string{""},
SecondClosestCommentLines: []string{""},
Members: []types.Member{
{
Name: "V",
Embedded: false,
CommentLines: []string{"V is the first field."},
Tags: `json:"v"`,
Type: &types.Type{
Kind: types.TypeParam,
Name: types.Name{
Name: "T",
},
},
},
},
TypeParams: map[string]*types.Type{
"T": {
Name: types.Name{
Name: "any",
},
Kind: types.Interface,
},
},
}
return &types.Type{
Name: types.Name{
Package: "k8s.io/gengo/v2/parser/testdata/generic-field",
Name: "Foo",
},
Kind: types.Struct,
CommentLines: []string{""},
SecondClosestCommentLines: []string{""},
Members: []types.Member{
{
Name: "B",
Embedded: false,
CommentLines: []string{""},
Tags: `json:"b"`,
Type: fieldType,
},
},
TypeParams: map[string]*types.Type{},
}
},
},
{
description: "generic multiple",
testFile: "./testdata/generic-multi",
Expand Down Expand Up @@ -973,12 +1030,20 @@ func TestStructParse(t *testing.T) {
recursiveT := &types.Type{
Name: types.Name{
Package: "k8s.io/gengo/v2/parser/testdata/generic-recursive",
Name: "DeepCopyable",
Name: "DeepCopyable[T]",
},
Kind: types.Interface,
CommentLines: []string{""},
SecondClosestCommentLines: []string{""},
Methods: map[string]*types.Type{},
TypeParams: map[string]*types.Type{
"T": {
Name: types.Name{
Name: "any",
},
Kind: types.Interface,
},
},
}
recursiveT.Methods["DeepCopy"] = &types.Type{
Name: types.Name{
Expand Down Expand Up @@ -1054,7 +1119,29 @@ func TestStructParse(t *testing.T) {
t.Fatalf("type %s not found", expected.Name.Name)
}
if e, a := expected, st; !reflect.DeepEqual(e, a) {
t.Errorf("wanted, got:\n%#v\n%#v", e, a)
t.Errorf("wanted, got:\n%#v\n%#v\n%s", e, a, cmp.Diff(e, a))
}
})
}
}

func TestGoNameToName(t *testing.T) {
testCases := []struct {
input string
expect types.Name
}{
{input: "foo", expect: types.Name{Name: "foo"}},
{input: "foo.bar", expect: types.Name{Package: "foo", Name: "bar"}},
{input: "foo.bar.baz", expect: types.Name{Package: "foo.bar", Name: "baz"}},
{input: "Foo[T]", expect: types.Name{Package: "", Name: "Foo[T]"}},
{input: "Foo[T any]", expect: types.Name{Package: "", Name: "Foo[T any]"}},
{input: "pkg.Foo[T]", expect: types.Name{Package: "pkg", Name: "Foo[T]"}},
{input: "pkg.Foo[T any]", expect: types.Name{Package: "pkg", Name: "Foo[T any]"}},
}
for _, tc := range testCases {
t.Run(tc.input, func(t *testing.T) {
if got, want := goNameToName(tc.input), tc.expect; !reflect.DeepEqual(got, want) {
t.Errorf("\nwant: %#v\ngot: %#v", want, got)
}
})
}
Expand Down
10 changes: 10 additions & 0 deletions v2/parser/testdata/generic-field/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package foo

type Blah[T any] struct {
// V is the first field.
V T `json:"v"`
}

type Foo struct {
B Blah[string] `json:"b"`
}