Fix defaulter-gen for recursive types#61
Conversation
| continue | ||
| } | ||
| if buildCallTreeForType(t, true, existingDefaulters, newDefaulters) != nil { | ||
| existingTypes := make(map[string]bool) |
There was a problem hiding this comment.
PTAL if this is the right way to do it.
| // 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.
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.
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.
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.
So I should check for t.Name?
There was a problem hiding this comment.
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.
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.
s/existingTypes/alreadyVisitedTypes/
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.
The name is wrong if you set it to false after you are done. Should be "currentlyBuildingTypes" or "typesInProgress" or something.
| // 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.
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. |
lavalamp
left a comment
There was a problem hiding this comment.
Sorry for not being clear but that wasn't quite what I had in mind.
|
|
||
| // callTreeForType contains fields necessary to call buildCallTreeForType. | ||
| type callTreeForType struct { | ||
| t *types.Type |
There was a problem hiding this comment.
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.
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.
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.
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.
All of these should look like if child := r.Build(c.t.Elem, false); child != 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.
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.
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.
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.
What I was trying to communicate is that you don't call newCallTreeForType here, you reuse c.
There was a problem hiding this comment.
I think I wrote an r where I meant a c--sorry about that.
There was a problem hiding this comment.
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.
Looking good--last question, should this be in a defer block? The return on line 496 skips it, for example.
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