Skip to content

Commit 2d337f5

Browse files
committed
Retool validator utils realType and unaliasType
Previous commits focused on checking for pointers and aliases more appropriately. What emerged was two functions `realType` which gave us the concrete native type (non-pointer, non-alias) an `unaliasType` which stripped aliasing but only until it hit pointerness. They were sort of inconsistently applied and in a few places I found the composition of them to be weird. I want to reframe those as: 1) `func nativeType(t) t` which removes all aliasing but retains pointerness. So for example: ``` type T1 string type T2 *T1 type T3 *T2 type Foo struct { Name *T3 } ``` Calling `nativeType()` on the `Name` member would yield `***string`. This is what we want in most places. We need to retain pointerness because (e.g.) we DO NOT support pointer to slice, and we want that to fail, but we DO support pointer to string and we want that to succeed, but `optional` needs to handle pointers and non-pointers differently. We filter totally disallowed things early on, but if a pointer to slice should sneak in somehow, this will refuse to generate bad code (fail in an obvious way). 2) `func nonPointer(t) t` which removes all pointerness (if present), but does not look past aliases. We needed to compose these both and call `nonPointer(nativeType(t))` in a few places. E.g. when applying `+k8s:format`, we need to know that the field is a string OR pointer to string. These two are more composable, I think. If you agree I can finish that off this weekend and ping the PR. If not, would like to hear your thoughts. Mostly the goal is put as many defensive checks in place as possible. New validators will copy these ones and I want it to be as clear as possible.
1 parent 835bb2e commit 2d337f5

File tree

6 files changed

+62
-50
lines changed

6 files changed

+62
-50
lines changed

staging/src/k8s.io/code-generator/cmd/validation-gen/validators/common.go

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,37 +40,64 @@ func getMemberByJSON(t *types.Type, jsonName string) *types.Member {
4040

4141
// isNilableType returns true if the argument type can be compared to nil.
4242
func isNilableType(t *types.Type) bool {
43-
switch unaliasType(t).Kind {
43+
for t.Kind == types.Alias {
44+
t = t.Underlying
45+
}
46+
switch t.Kind {
4447
case types.Pointer, types.Map, types.Slice, types.Interface: // Note: Arrays are not nilable
4548
return true
4649
}
4750
return false
4851
}
4952

50-
// unaliasType returns the underlying type of the specified type if it is an
51-
// alias, otherwise returns the type itself.
52-
func unaliasType(t *types.Type) *types.Type {
53-
for t.Kind == types.Alias {
54-
t = t.Underlying
55-
}
56-
return t
57-
}
58-
59-
// realType returns the underlying type of a type, unwrapping aliases and
60-
// dropping pointerness.
61-
func realType(t *types.Type) *types.Type {
53+
// nativeType returns the Go native type of the argument type, with any
54+
// intermediate typedefs removed. Go itself already flattens typedefs, but this
55+
// handles it in the unlikely event that we ever fix that.
56+
//
57+
// Examples:
58+
// * Trivial:
59+
// - given `int`, returns `int`
60+
// - given `*int`, returns `*int`
61+
// - given `[]int`, returns `[]int`
62+
//
63+
// * Typedefs
64+
// - given `type X int; X`, returns `int`
65+
// - given `type X int; []X`, returns `[]X`
66+
//
67+
// * Typedefs and pointers:
68+
// - given `type X int; *X`, returns `*int`
69+
// - given `type X *int; *X`, returns `**int`
70+
// - given `type X []int; X`, returns `[]int`
71+
// - given `type X []int; *X`, returns `*[]int`
72+
func nativeType(t *types.Type) *types.Type {
73+
ptrs := 0
6274
for {
6375
if t.Kind == types.Alias {
6476
t = t.Underlying
6577
} else if t.Kind == types.Pointer {
78+
ptrs++
6679
t = t.Elem
6780
} else {
6881
break
6982
}
7083
}
84+
for range ptrs {
85+
t = types.PointerTo(t)
86+
}
87+
return t
88+
}
89+
90+
// nonPointer returns the value-type of a possibly pointer type. If type is not
91+
// a pointer, it returns the input type.
92+
func nonPointer(t *types.Type) *types.Type {
93+
for t.Kind == types.Pointer {
94+
t = t.Elem
95+
}
7196
return t
7297
}
7398

99+
// rootTypeString returns a string representation of the relationship between
100+
// src and dst types, for use in error messages.
74101
func rootTypeString(src, dst *types.Type) string {
75102
if src == dst {
76103
return src.String()

staging/src/k8s.io/code-generator/cmd/validation-gen/validators/each.go

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,8 @@ func (listTypeTagValidator) ValidScopes() sets.Set[Scope] {
7676
}
7777

7878
func (lttv listTypeTagValidator) GetValidations(context Context, _ []string, payload string) (Validations, error) {
79-
// We don't support pointers to lists, but other validators use realType()
80-
// for this sort of check, so let's be consistent.
81-
t := realType(context.Type)
79+
// NOTE: pointers to lists are not supported, so we should never see a pointer here.
80+
t := nativeType(context.Type)
8281
if t.Kind != types.Slice && t.Kind != types.Array {
8382
return Validations{}, fmt.Errorf("can only be used on list types")
8483
}
@@ -87,8 +86,8 @@ func (lttv listTypeTagValidator) GetValidations(context Context, _ []string, pay
8786
case "atomic", "set":
8887
// Allowed but no special handling.
8988
case "map":
90-
// NOTE: maps of pointers are not supported, and that is enforced way before this point.
91-
if realType(t.Elem).Kind != types.Struct {
89+
// NOTE: maps of pointers are not supported, so we should never see a pointer here.
90+
if nativeType(t.Elem).Kind != types.Struct {
9291
return Validations{}, fmt.Errorf("only lists of structs can be list-maps")
9392
}
9493

@@ -135,23 +134,21 @@ func (listMapKeyTagValidator) ValidScopes() sets.Set[Scope] {
135134
}
136135

137136
func (lmktv listMapKeyTagValidator) GetValidations(context Context, _ []string, payload string) (Validations, error) {
138-
// We don't support pointers to lists, but other validators use realType()
139-
// for this sort of check, so let's be consistent.
140-
t := realType(context.Type)
137+
// NOTE: pointers to lists are not supported, so we should never see a pointer here.
138+
t := nativeType(context.Type)
141139
if t.Kind != types.Slice && t.Kind != types.Array {
142140
return Validations{}, fmt.Errorf("can only be used on list types")
143141
}
144-
if realType(t.Elem).Kind != types.Struct {
142+
// NOTE: lists of pointers are not supported, so we should never see a pointer here.
143+
if nativeType(t.Elem).Kind != types.Struct {
145144
return Validations{}, fmt.Errorf("only lists of structs can be list-maps")
146145
}
147146

148147
var fieldName string
149-
if memb := getMemberByJSON(realType(t.Elem), payload); memb == nil {
148+
if memb := getMemberByJSON(nativeType(t.Elem), payload); memb == nil {
150149
return Validations{}, fmt.Errorf("no field for JSON name %q", payload)
151-
} else if k := realType(memb.Type).Kind; k != types.Builtin {
152-
return Validations{}, fmt.Errorf("only primitive types can be list-map keys, not %s", k)
153-
} else if isPointer(memb.Type) {
154-
return Validations{}, fmt.Errorf("pointer types cannot be list-map keys")
150+
} else if k := nativeType(memb.Type).Kind; k != types.Builtin {
151+
return Validations{}, fmt.Errorf("only primitive types can be list-map keys (%s)", k)
155152
} else {
156153
fieldName = memb.Name
157154
}
@@ -167,16 +164,6 @@ func (lmktv listMapKeyTagValidator) GetValidations(context Context, _ []string,
167164
return Validations{}, nil
168165
}
169166

170-
func isPointer(t *types.Type) bool {
171-
if t.Kind == types.Pointer {
172-
return true
173-
}
174-
if t.Kind == types.Alias {
175-
return isPointer(t.Underlying)
176-
}
177-
return false
178-
}
179-
180167
func (lmktv listMapKeyTagValidator) Docs() TagDoc {
181168
doc := TagDoc{
182169
Tag: lmktv.TagName(),
@@ -217,9 +204,8 @@ var (
217204
)
218205

219206
func (evtv eachValTagValidator) GetValidations(context Context, _ []string, payload string) (Validations, error) {
220-
// We don't support pointers to lists, but other validators use realType()
221-
// for this sort of check, so let's be consistent.
222-
t := realType(context.Type)
207+
// NOTE: pointers to lists and maps are not supported, so we should never see a pointer here.
208+
t := nativeType(context.Type)
223209
switch t.Kind {
224210
case types.Slice, types.Array, types.Map:
225211
default:
@@ -351,9 +337,8 @@ var (
351337
)
352338

353339
func (ektv eachKeyTagValidator) GetValidations(context Context, _ []string, payload string) (Validations, error) {
354-
// We don't support pointers to lists, but other validators use realType()
355-
// for this sort of check, so let's be consistent.
356-
t := realType(context.Type)
340+
// NOTE: pointers to lists are not supported, so we should never see a pointer here.
341+
t := nativeType(context.Type)
357342
if t.Kind != types.Map {
358343
return Validations{}, fmt.Errorf("can only be used on map types")
359344
}

staging/src/k8s.io/code-generator/cmd/validation-gen/validators/immutable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ var (
5151
func (immutableTagValidator) GetValidations(context Context, _ []string, payload string) (Validations, error) {
5252
var result Validations
5353

54-
if realType(context.Type).Kind == types.Builtin {
54+
if nonPointer(nativeType(context.Type)).Kind == types.Builtin {
5555
// This is a minor optimization to just compare primitive values when
5656
// possible. Slices and maps are not comparable, and structs might hold
5757
// pointer fields, which are directly comparable but not what we need.

staging/src/k8s.io/code-generator/cmd/validation-gen/validators/limits.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (minimumTagValidator) GetValidations(context Context, _ []string, payload s
5757

5858
// This tag can apply to value and pointer fields, as well as typedefs
5959
// (which should never be pointers). We need to check the concrete type.
60-
if t := realType(context.Type); !types.IsInteger(t) {
60+
if t := nonPointer(nativeType(context.Type)); !types.IsInteger(t) {
6161
return result, fmt.Errorf("can only be used on integer types (%s)", rootTypeString(context.Type, t))
6262
}
6363

staging/src/k8s.io/code-generator/cmd/validation-gen/validators/required.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func (rtv requirednessTagValidator) doRequired(context Context) (Validations, er
9090
// originally defined as a value-type or a pointer-type in the API. This
9191
// one does. Since Go doesn't do partial specialization of templates, we
9292
// do manual dispatch here.
93-
switch unaliasType(context.Type).Kind {
93+
switch nativeType(context.Type).Kind {
9494
case types.Slice:
9595
return Validations{Functions: []FunctionGen{Function(requiredTagName, ShortCircuit, requiredSliceValidator)}}, nil
9696
case types.Map:
@@ -161,7 +161,7 @@ func (rtv requirednessTagValidator) doOptional(context Context) (Validations, er
161161
// originally defined as a value-type or a pointer-type in the API. This
162162
// one does. Since Go doesn't do partial specialization of templates, we
163163
// do manual dispatch here.
164-
switch unaliasType(context.Type).Kind {
164+
switch nativeType(context.Type).Kind {
165165
case types.Slice:
166166
return Validations{Functions: []FunctionGen{Function(optionalTagName, ShortCircuit|NonError, optionalSliceValidator)}}, nil
167167
case types.Map:
@@ -182,7 +182,7 @@ func (rtv requirednessTagValidator) doOptional(context Context) (Validations, er
182182
// hasZeroDefault returns whether the field has a default value and whether
183183
// that default value is the zero value for the field's type.
184184
func (rtv requirednessTagValidator) hasZeroDefault(context Context) (bool, bool, error) {
185-
t := realType(context.Type)
185+
t := nonPointer(nativeType(context.Type))
186186
// This validator only applies to fields, so Member must be valid.
187187
tagsByName, err := gengo.ExtractFunctionStyleCommentTags("+", []string{defaultTagName}, context.Member.CommentLines)
188188
if err != nil {
@@ -261,7 +261,7 @@ func (requirednessTagValidator) doForbidden(context Context) (Validations, error
261261
// optional check and short-circuit (but without error). Why? For
262262
// example, this prevents any further validation from trying to run on a
263263
// nil pointer.
264-
switch unaliasType(context.Type).Kind {
264+
switch nativeType(context.Type).Kind {
265265
case types.Slice:
266266
return Validations{
267267
Functions: []FunctionGen{

staging/src/k8s.io/code-generator/cmd/validation-gen/validators/subfield.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ var (
5656
func (stv subfieldTagValidator) GetValidations(context Context, args []string, payload string) (Validations, error) {
5757
// This tag can apply to value and pointer fields, as well as typedefs
5858
// (which should never be pointers). We need to check the concrete type.
59-
t := realType(context.Type)
59+
t := nonPointer(nativeType(context.Type))
6060
if t.Kind != types.Struct {
6161
return Validations{}, fmt.Errorf("can only be used on struct types")
6262
}

0 commit comments

Comments
 (0)