-
Notifications
You must be signed in to change notification settings - Fork 194
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
Fix defaulter-gen for recursive types #61
Conversation
@@ -320,7 +320,8 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat | |||
if d.object != nil { | |||
continue | |||
} | |||
if buildCallTreeForType(t, true, existingDefaulters, newDefaulters) != nil { | |||
existingTypes := make(map[string]bool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PTAL if this is the right way to do it.
@@ -396,7 +397,7 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat | |||
// that could be or will be generated. If newDefaulters has an entry for a type, but the 'object' field is nil, | |||
// this function skips adding that defaulter - this allows us to avoid generating object defaulter functions for | |||
// list types that call empty defaulters. | |||
func buildCallTreeForType(t *types.Type, root bool, existingDefaulters, newDefaulters defaulterFuncMap) *callNode { | |||
func buildCallTreeForType(t *types.Type, root bool, existingDefaulters, newDefaulters defaulterFuncMap, existingTypes map[string]bool) *callNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you document existingTypes? Is it feasible to have a recursive type in some test? Are there tests here in the generator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will document it.
Currently there are no tests. But I'll add one.
return parent | ||
} | ||
// if type doesn't exist, mark it as existing | ||
existingTypes[t.Name.Name] = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we check that Name
is actually files, i.e. not an anonymous type? Otherwise, we stop too early for those, don't we?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I should check for t.Name
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I read the type godoc correctly, Name can be empty.
Can you link a commit for the Kubernetes defaulters? Do they change after this PR? |
This could really use output tests like https://github.com/kubernetes/gengo/tree/master/examples/deepcopy-gen/output_tests |
331e754
to
ba7ac3b
Compare
Updated the defaulter code. PTAL. I have added some tests. However, I come across the following error when I run It works when there is no After making the changes in this PR and fixing the conversion in kubernetes/kubernetes#47263, nothing breaks/changes in Kubernetes (tested against HEAD). This PR shows the cumulative changes in both the defaulter and converter: nikhita/kubernetes#3. |
@@ -0,0 +1,36 @@ | |||
/* | |||
Copyright 2016 The Kubernetes Authors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, will fix the year after the review.
9929cff
to
ba7ac3b
Compare
Added tests on top of #55 here: caesarxuchao#1. |
lgtm. @thockin ptal at https://github.com/caesarxuchao/gengo/pull/1/files for example output. |
if buildCallTreeForType(t, true, existingDefaulters, newDefaulters) != nil { | ||
// existingTypes keeps track of types that have already been visited in the tree. | ||
// This is used to avoid recursion for recursive types. | ||
existingTypes := make(map[*types.Type]bool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/existingTypes/alreadyVisitedTypes/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
ba7ac3b
to
5f0312b
Compare
|
||
// The type now acts as a parent, not a nested recursive type. | ||
// We can now build the tree for it safely. | ||
alreadyVisitedTypes[t] = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name is wrong if you set it to false after you are done. Should be "currentlyBuildingTypes" or "typesInProgress" or something.
@@ -396,7 +399,7 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat | |||
// that could be or will be generated. If newDefaulters has an entry for a type, but the 'object' field is nil, | |||
// this function skips adding that defaulter - this allows us to avoid generating object defaulter functions for | |||
// list types that call empty defaulters. | |||
func buildCallTreeForType(t *types.Type, root bool, existingDefaulters, newDefaulters defaulterFuncMap) *callNode { | |||
func buildCallTreeForType(t *types.Type, root bool, existingDefaulters, newDefaulters defaulterFuncMap, alreadyVisitedTypes map[*types.Type]bool) *callNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider making a struct to hold all of the parameters that don't change, and make this function a method on that struct. Will make the code a lot easier to read.
(happy to lgtm & merge after those comments are addressed) |
5f0312b
to
ce68359
Compare
@lavalamp Updated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not being clear but that wasn't quite what I had in mind.
@@ -387,6 +391,25 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat | |||
return packages | |||
} | |||
|
|||
// callTreeForType contains fields necessary to call buildCallTreeForType. | |||
type callTreeForType struct { | |||
t *types.Type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove; this struct should have only the immutable parameters.
// callTreeForType contains fields necessary to call buildCallTreeForType. | ||
type callTreeForType struct { | ||
t *types.Type | ||
root bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove; this struct should have only the immutable parameters.
currentlyBuildingTypes map[*types.Type]bool | ||
} | ||
|
||
func NewCallTreeForType(t *types.Type, root bool, existingDefaulters, newDefaulters defaulterFuncMap, currentlyBuildingTypes map[*types.Type]bool) callTreeForType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delete this or change to:
func NewCallTreeForType(existingDefaulters, newDefaulters defaulterFuncMap) *callTreeForType {
return &callTreeForType{
existingDefaulters: existingDefaulters,
newDefaulters: newDefaulters,
currentlyBuildingTypes: make(map[*types.Type]bool),
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't initialize currentlyBuildingTypes in newCallTreeForType because when this is encountered:
r := newCallTreeForType(c.existingDefaulters, c.newDefaulters)
if child := r.build(t.Elem, false); child != nil
it will create a new map for currentlyBuildingTypes - destroying our purpose for storing the types that are currently building. :)
case types.Pointer: | ||
if child := buildCallTreeForType(t.Elem, false, existingDefaulters, newDefaulters); child != nil { | ||
r := NewCallTreeForType(c.t.Elem, false, c.existingDefaulters, c.newDefaulters, c.currentlyBuildingTypes) | ||
if child := r.buildCallTreeForType(); child != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these should look like if child := r.Build(c.t.Elem, false); child != nil {
@@ -396,22 +419,22 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat | |||
// that could be or will be generated. If newDefaulters has an entry for a type, but the 'object' field is nil, | |||
// this function skips adding that defaulter - this allows us to avoid generating object defaulter functions for | |||
// list types that call empty defaulters. | |||
func buildCallTreeForType(t *types.Type, root bool, existingDefaulters, newDefaulters defaulterFuncMap) *callNode { | |||
func (c callTreeForType) buildCallTreeForType() *callNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func (c *callTreeForType) Build(t *types.Type, root bool) *callNode {
// This is used to avoid recursion for recursive types. | ||
currentlyBuildingTypes := make(map[*types.Type]bool) | ||
c := NewCallTreeForType(t, true, existingDefaulters, newDefaulters, currentlyBuildingTypes) | ||
if c.buildCallTreeForType() != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a one-liner:
if NewCallTreeForType(existingDefaulters, newDefaulters).Build(t, true) != nil {
currentlyBuildingTypes map[*types.Type]bool | ||
} | ||
|
||
func NewCallTreeForType(t *types.Type, root bool, existingDefaulters, newDefaulters defaulterFuncMap, currentlyBuildingTypes map[*types.Type]bool) callTreeForType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually both this and Build should be private.
ce68359
to
644e318
Compare
@lavalamp Updated. PTAL, thanks. |
switch t.Kind { | ||
case types.Pointer: | ||
if child := buildCallTreeForType(t.Elem, false, existingDefaulters, newDefaulters); child != nil { | ||
r := newCallTreeForType(c.existingDefaulters, c.newDefaulters, c.currentlyBuildingTypes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I was trying to communicate is that you don't call newCallTreeForType here, you reuse c
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I wrote an r
where I meant a c
--sorry about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, sorry - should have done this in the first place.
Updated. Please check if this is better.
644e318
to
9a0932b
Compare
|
||
// The type now acts as a parent, not a nested recursive type. | ||
// We can now build the tree for it safely. | ||
c.currentlyBuildingTypes[t] = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good--last question, should this be in a defer block? The return on line 496 skips it, for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Done.
Rename Address comments Address further comments Use defer
9a0932b
to
96fe5d0
Compare
LGTM, thanks for bearing with me :) |
Automatic merge from submit-queue apiextensions: validation for customresources - [x] Add types for validation of CustomResources - [x] Fix conversion-gen: #49747 - [x] Fix defaulter-gen: kubernetes/gengo#61 - [x] Convert to OpenAPI types - [x] Validate CR using go-openapi - [x] Validate CRD Schema - [x] Add integration tests - [x] Fix round trip tests: #51204 - [x] Add custom fuzzer functions - [x] Add custom conversion functions - [x] Fix data race while updating CRD: #50098 - [x] Add feature gate for CustomResourceValidation - [x] Fix protobuf generation Proposal: kubernetes/community#708 Additional discussion: #49879, #50625 **Release note**: ```release-note Add validation for CustomResources via JSON Schema. ``` /cc @sttts @deads2k
Automatic merge from submit-queue apiextensions: validation for customresources - [x] Add types for validation of CustomResources - [x] Fix conversion-gen: #49747 - [x] Fix defaulter-gen: kubernetes/gengo#61 - [x] Convert to OpenAPI types - [x] Validate CR using go-openapi - [x] Validate CRD Schema - [x] Add integration tests - [x] Fix round trip tests: #51204 - [x] Add custom fuzzer functions - [x] Add custom conversion functions - [x] Fix data race while updating CRD: #50098 - [x] Add feature gate for CustomResourceValidation - [x] Fix protobuf generation Proposal: kubernetes/community#708 Additional discussion: kubernetes/kubernetes#49879, kubernetes/kubernetes#50625 **Release note**: ```release-note Add validation for CustomResources via JSON Schema. ``` /cc @sttts @deads2k Kubernetes-commit: 4457e43e7b789586096bfb564330295cf0438e70
For recursive types, the defaulter currently goes into an infinite recursion.
Fix to support recursive types.
/cc @sttts