mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 05:27:21 +00:00
Explicit conversion generator
This commit is contained in:
parent
f08dfb4cb6
commit
5823fffc23
@ -21,6 +21,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -41,6 +42,9 @@ const (
|
|||||||
// e.g., "+k8s:conversion-gen=false" in a type's comment will let
|
// e.g., "+k8s:conversion-gen=false" in a type's comment will let
|
||||||
// conversion-gen skip that type.
|
// conversion-gen skip that type.
|
||||||
tagName = "k8s:conversion-gen"
|
tagName = "k8s:conversion-gen"
|
||||||
|
// e.g. "+k8s:conversion-gen:explicit-from=net/url.Values" in the type comment
|
||||||
|
// will result in generating conversion from net/url.Values.
|
||||||
|
explicitFromTagName = "k8s:conversion-gen:explicit-from"
|
||||||
// e.g., "+k8s:conversion-gen-external-types=<type-pkg>" in doc.go, where
|
// e.g., "+k8s:conversion-gen-external-types=<type-pkg>" in doc.go, where
|
||||||
// <type-pkg> is the relative path to the package the types are defined in.
|
// <type-pkg> is the relative path to the package the types are defined in.
|
||||||
externalTypesTagName = "k8s:conversion-gen-external-types"
|
externalTypesTagName = "k8s:conversion-gen-external-types"
|
||||||
@ -50,6 +54,10 @@ func extractTag(comments []string) []string {
|
|||||||
return types.ExtractCommentTags("+", comments)[tagName]
|
return types.ExtractCommentTags("+", comments)[tagName]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractExplicitFromTag(comments []string) []string {
|
||||||
|
return types.ExtractCommentTags("+", comments)[explicitFromTagName]
|
||||||
|
}
|
||||||
|
|
||||||
func extractExternalTypesTag(comments []string) []string {
|
func extractExternalTypesTag(comments []string) []string {
|
||||||
return types.ExtractCommentTags("+", comments)[externalTypesTagName]
|
return types.ExtractCommentTags("+", comments)[externalTypesTagName]
|
||||||
}
|
}
|
||||||
@ -240,14 +248,22 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
|
|||||||
peerPkgs := extractTag(pkg.Comments)
|
peerPkgs := extractTag(pkg.Comments)
|
||||||
if peerPkgs != nil {
|
if peerPkgs != nil {
|
||||||
klog.V(5).Infof(" tags: %q", peerPkgs)
|
klog.V(5).Infof(" tags: %q", peerPkgs)
|
||||||
|
if len(peerPkgs) == 1 && peerPkgs[0] == "false" {
|
||||||
|
// If a single +k8s:conversion-gen=false tag is defined, we still want
|
||||||
|
// the generator to fire for this package for explicit conversions, but
|
||||||
|
// we are clearing the peerPkgs to not generate any standard conversions.
|
||||||
|
peerPkgs = nil
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
klog.V(5).Infof(" no tag")
|
klog.V(5).Infof(" no tag")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
skipUnsafe := false
|
skipUnsafe := false
|
||||||
if customArgs, ok := arguments.CustomArgs.(*conversionargs.CustomArgs); ok {
|
if customArgs, ok := arguments.CustomArgs.(*conversionargs.CustomArgs); ok {
|
||||||
|
if len(peerPkgs) > 0 {
|
||||||
peerPkgs = append(peerPkgs, customArgs.BasePeerDirs...)
|
peerPkgs = append(peerPkgs, customArgs.BasePeerDirs...)
|
||||||
peerPkgs = append(peerPkgs, customArgs.ExtraPeerDirs...)
|
peerPkgs = append(peerPkgs, customArgs.ExtraPeerDirs...)
|
||||||
|
}
|
||||||
skipUnsafe = customArgs.SkipUnsafe
|
skipUnsafe = customArgs.SkipUnsafe
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,6 +477,7 @@ type genConversion struct {
|
|||||||
manualConversions conversionFuncMap
|
manualConversions conversionFuncMap
|
||||||
imports namer.ImportTracker
|
imports namer.ImportTracker
|
||||||
types []*types.Type
|
types []*types.Type
|
||||||
|
explicitConversions []conversionPair
|
||||||
skippedFields map[*types.Type][]string
|
skippedFields map[*types.Type][]string
|
||||||
useUnsafe TypesEqual
|
useUnsafe TypesEqual
|
||||||
}
|
}
|
||||||
@ -476,6 +493,7 @@ func NewGenConversion(sanitizedName, typesPackage, outputPackage string, manualC
|
|||||||
manualConversions: manualConversions,
|
manualConversions: manualConversions,
|
||||||
imports: generator.NewImportTracker(),
|
imports: generator.NewImportTracker(),
|
||||||
types: []*types.Type{},
|
types: []*types.Type{},
|
||||||
|
explicitConversions: []conversionPair{},
|
||||||
skippedFields: map[*types.Type][]string{},
|
skippedFields: map[*types.Type][]string{},
|
||||||
useUnsafe: useUnsafe,
|
useUnsafe: useUnsafe,
|
||||||
}
|
}
|
||||||
@ -534,7 +552,28 @@ func (g *genConversion) convertibleOnlyWithinPackage(inType, outType *types.Type
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getExplicitFromTypes(t *types.Type) []types.Name {
|
||||||
|
comments := append(t.SecondClosestCommentLines, t.CommentLines...)
|
||||||
|
paths := extractExplicitFromTag(comments)
|
||||||
|
result := []types.Name{}
|
||||||
|
for _, path := range paths {
|
||||||
|
items := strings.Split(path, ".")
|
||||||
|
if len(items) != 2 {
|
||||||
|
klog.Errorf("Unexpected k8s:conversion-gen:explicit-from tag: %s", path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case items[0] == "net/url" && items[1] == "Values":
|
||||||
|
default:
|
||||||
|
klog.Fatalf("Not supported k8s:conversion-gen:explicit-from tag: %s", path)
|
||||||
|
}
|
||||||
|
result = append(result, types.Name{Package: items[0], Name: items[1]})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (g *genConversion) Filter(c *generator.Context, t *types.Type) bool {
|
func (g *genConversion) Filter(c *generator.Context, t *types.Type) bool {
|
||||||
|
convertibleWithPeer := func() bool {
|
||||||
peerType := getPeerTypeFor(c, t, g.peerPackages)
|
peerType := getPeerTypeFor(c, t, g.peerPackages)
|
||||||
if peerType == nil {
|
if peerType == nil {
|
||||||
return false
|
return false
|
||||||
@ -542,9 +581,26 @@ func (g *genConversion) Filter(c *generator.Context, t *types.Type) bool {
|
|||||||
if !g.convertibleOnlyWithinPackage(t, peerType) {
|
if !g.convertibleOnlyWithinPackage(t, peerType) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
g.types = append(g.types, t)
|
g.types = append(g.types, t)
|
||||||
return true
|
return true
|
||||||
|
}()
|
||||||
|
|
||||||
|
explicitlyConvertible := func() bool {
|
||||||
|
inTypes := getExplicitFromTypes(t)
|
||||||
|
if len(inTypes) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range inTypes {
|
||||||
|
pair := conversionPair{
|
||||||
|
inType: &types.Type{Name: inTypes[i]},
|
||||||
|
outType: t,
|
||||||
|
}
|
||||||
|
g.explicitConversions = append(g.explicitConversions, pair)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}()
|
||||||
|
|
||||||
|
return convertibleWithPeer || explicitlyConvertible
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *genConversion) isOtherPackage(pkg string) bool {
|
func (g *genConversion) isOtherPackage(pkg string) bool {
|
||||||
@ -618,6 +674,12 @@ func (g *genConversion) Init(c *generator.Context, w io.Writer) error {
|
|||||||
args = argsFromType(peerType, t).With("Scope", types.Ref(conversionPackagePath, "Scope"))
|
args = argsFromType(peerType, t).With("Scope", types.Ref(conversionPackagePath, "Scope"))
|
||||||
sw.Do("if err := s.AddGeneratedConversionFunc((*$.inType|raw$)(nil), (*$.outType|raw$)(nil), func(a, b interface{}, scope $.Scope|raw$) error { return "+nameTmpl+"(a.(*$.inType|raw$), b.(*$.outType|raw$), scope) }); err != nil { return err }\n", args)
|
sw.Do("if err := s.AddGeneratedConversionFunc((*$.inType|raw$)(nil), (*$.outType|raw$)(nil), func(a, b interface{}, scope $.Scope|raw$) error { return "+nameTmpl+"(a.(*$.inType|raw$), b.(*$.outType|raw$), scope) }); err != nil { return err }\n", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range g.explicitConversions {
|
||||||
|
args := argsFromType(g.explicitConversions[i].inType, g.explicitConversions[i].outType).With("Scope", types.Ref(conversionPackagePath, "Scope"))
|
||||||
|
sw.Do("if err := s.AddGeneratedConversionFunc((*$.inType|raw$)(nil), (*$.outType|raw$)(nil), func(a, b interface{}, scope $.Scope|raw$) error { return "+nameTmpl+"(a.(*$.inType|raw$), b.(*$.outType|raw$), scope) }); err != nil { return err }\n", args)
|
||||||
|
}
|
||||||
|
|
||||||
var pairs []conversionPair
|
var pairs []conversionPair
|
||||||
for pair, t := range g.manualConversions {
|
for pair, t := range g.manualConversions {
|
||||||
if t.Name.Package != g.outputPackage {
|
if t.Name.Package != g.outputPackage {
|
||||||
@ -644,10 +706,32 @@ func (g *genConversion) Init(c *generator.Context, w io.Writer) error {
|
|||||||
|
|
||||||
func (g *genConversion) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
|
func (g *genConversion) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
|
||||||
klog.V(5).Infof("generating for type %v", t)
|
klog.V(5).Infof("generating for type %v", t)
|
||||||
peerType := getPeerTypeFor(c, t, g.peerPackages)
|
|
||||||
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
||||||
|
|
||||||
|
if peerType := getPeerTypeFor(c, t, g.peerPackages); peerType != nil {
|
||||||
g.generateConversion(t, peerType, sw)
|
g.generateConversion(t, peerType, sw)
|
||||||
g.generateConversion(peerType, t, sw)
|
g.generateConversion(peerType, t, sw)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, inTypeName := range getExplicitFromTypes(t) {
|
||||||
|
inPkg, ok := c.Universe[inTypeName.Package]
|
||||||
|
if !ok {
|
||||||
|
klog.Errorf("Unrecognized package: %s", inTypeName.Package)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inType, ok := inPkg.Types[inTypeName.Name]
|
||||||
|
if !ok {
|
||||||
|
klog.Errorf("Unrecognized type in package %s: %s", inTypeName.Package, inTypeName.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case inType.Name.Package == "net/url" && inType.Name.Name == "Values":
|
||||||
|
g.generateFromUrlValues(inType, t, sw)
|
||||||
|
default:
|
||||||
|
klog.Errorf("Not supported input type: %#v", inType.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sw.Error()
|
return sw.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -972,6 +1056,124 @@ func (g *genConversion) doUnknown(inType, outType *types.Type, sw *generator.Sni
|
|||||||
sw.Do("// FIXME: Type $.|raw$ is unsupported.\n", inType)
|
sw.Do("// FIXME: Type $.|raw$ is unsupported.\n", inType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *genConversion) generateFromUrlValues(inType, outType *types.Type, sw *generator.SnippetWriter) {
|
||||||
|
args := generator.Args{
|
||||||
|
"inType": inType,
|
||||||
|
"outType": outType,
|
||||||
|
"Scope": types.Ref(conversionPackagePath, "Scope"),
|
||||||
|
}
|
||||||
|
sw.Do("func auto"+nameTmpl+"(in *$.inType|raw$, out *$.outType|raw$, s $.Scope|raw$) error {\n", args)
|
||||||
|
for _, outMember := range outType.Members {
|
||||||
|
jsonTag := reflect.StructTag(outMember.Tags).Get("json")
|
||||||
|
index := strings.Index(jsonTag, ",")
|
||||||
|
if index == -1 {
|
||||||
|
index = len(jsonTag)
|
||||||
|
}
|
||||||
|
if index == 0 {
|
||||||
|
memberArgs := generator.Args{
|
||||||
|
"name": outMember.Name,
|
||||||
|
}
|
||||||
|
sw.Do("// WARNING: Field $.name$ does not have json tag, skipping.\n\n", memberArgs)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
memberArgs := generator.Args{
|
||||||
|
"name": outMember.Name,
|
||||||
|
"tag": jsonTag[:index],
|
||||||
|
}
|
||||||
|
sw.Do("if values, ok := map[string][]string(*in)[\"$.tag$\"]; ok && len(values) > 0 {\n", memberArgs)
|
||||||
|
g.fromValuesEntry(inType.Underlying.Elem, outMember, sw)
|
||||||
|
sw.Do("} else {\n", nil)
|
||||||
|
g.setZeroValue(outMember, sw)
|
||||||
|
sw.Do("}\n", nil)
|
||||||
|
}
|
||||||
|
sw.Do("return nil\n", nil)
|
||||||
|
sw.Do("}\n\n", nil)
|
||||||
|
|
||||||
|
if _, found := g.preexists(inType, outType); found {
|
||||||
|
// There is a public manual Conversion method: use it.
|
||||||
|
} else {
|
||||||
|
// Emit a public conversion function.
|
||||||
|
sw.Do("// "+nameTmpl+" is an autogenerated conversion function.\n", args)
|
||||||
|
sw.Do("func "+nameTmpl+"(in *$.inType|raw$, out *$.outType|raw$, s $.Scope|raw$) error {\n", args)
|
||||||
|
sw.Do("return auto"+nameTmpl+"(in, out, s)\n", args)
|
||||||
|
sw.Do("}\n\n", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *genConversion) fromValuesEntry(inType *types.Type, outMember types.Member, sw *generator.SnippetWriter) {
|
||||||
|
memberArgs := generator.Args{
|
||||||
|
"name": outMember.Name,
|
||||||
|
"type": outMember.Type,
|
||||||
|
}
|
||||||
|
if function, ok := g.preexists(inType, outMember.Type); ok {
|
||||||
|
args := memberArgs.With("function", function)
|
||||||
|
sw.Do("if err := $.function|raw$(&values, &out.$.name$, s); err != nil {\n", args)
|
||||||
|
sw.Do("return err\n", nil)
|
||||||
|
sw.Do("}\n", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case outMember.Type == types.String:
|
||||||
|
sw.Do("out.$.name$ = values[0]\n", memberArgs)
|
||||||
|
case g.useUnsafe.Equal(inType, outMember.Type):
|
||||||
|
args := memberArgs.With("Pointer", types.Ref("unsafe", "Pointer"))
|
||||||
|
switch inType.Kind {
|
||||||
|
case types.Pointer:
|
||||||
|
sw.Do("out.$.name$ = ($.type|raw$)($.Pointer|raw$(&values))\n", args)
|
||||||
|
case types.Map, types.Slice:
|
||||||
|
sw.Do("out.$.name$ = *(*$.type|raw$)($.Pointer|raw$(&values))\n", args)
|
||||||
|
default:
|
||||||
|
// TODO: Support other types to allow more auto-conversions.
|
||||||
|
sw.Do("// FIXME: out.$.name$ is of not yet supported type and requires manual conversion\n", memberArgs)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: Support other types to allow more auto-conversions.
|
||||||
|
sw.Do("// FIXME: out.$.name$ is of not yet supported type and requires manual conversion\n", memberArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *genConversion) setZeroValue(outMember types.Member, sw *generator.SnippetWriter) {
|
||||||
|
outMemberType := unwrapAlias(outMember.Type)
|
||||||
|
memberArgs := generator.Args{
|
||||||
|
"name": outMember.Name,
|
||||||
|
"alias": outMember.Type,
|
||||||
|
"type": outMemberType,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch outMemberType.Kind {
|
||||||
|
case types.Builtin:
|
||||||
|
switch outMemberType {
|
||||||
|
case types.String:
|
||||||
|
sw.Do("out.$.name$ = \"\"\n", memberArgs)
|
||||||
|
case types.Int64, types.Int32, types.Int16, types.Int, types.Uint64, types.Uint32, types.Uint16, types.Uint:
|
||||||
|
sw.Do("out.$.name$ = 0\n", memberArgs)
|
||||||
|
case types.Uintptr, types.Byte:
|
||||||
|
sw.Do("out.$.name$ = 0\n", memberArgs)
|
||||||
|
case types.Float64, types.Float32, types.Float:
|
||||||
|
sw.Do("out.$.name$ = 0\n", memberArgs)
|
||||||
|
case types.Bool:
|
||||||
|
sw.Do("out.$.name$ = false\n", memberArgs)
|
||||||
|
default:
|
||||||
|
sw.Do("// FIXME: out.$.name$ is of unsupported type and requires manual conversion\n", memberArgs)
|
||||||
|
}
|
||||||
|
case types.Struct:
|
||||||
|
if outMemberType == outMember.Type {
|
||||||
|
sw.Do("out.$.name$ = $.type|raw${}\n", memberArgs)
|
||||||
|
} else {
|
||||||
|
sw.Do("out.$.name$ = $.alias|raw$($.type|raw${})\n", memberArgs)
|
||||||
|
}
|
||||||
|
case types.Map, types.Slice, types.Pointer:
|
||||||
|
sw.Do("out.$.name$ = nil\n", memberArgs)
|
||||||
|
case types.Alias:
|
||||||
|
// outMemberType was already unwrapped from aliases - so that should never happen.
|
||||||
|
sw.Do("// FIXME: unexpected error for out.$.name$\n", memberArgs)
|
||||||
|
case types.Interface, types.Array:
|
||||||
|
sw.Do("out.$.name$ = nil\n", memberArgs)
|
||||||
|
default:
|
||||||
|
sw.Do("// FIXME: out.$.name$ is of unsupported type and requires manual conversion\n", memberArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isDirectlyAssignable(inType, outType *types.Type) bool {
|
func isDirectlyAssignable(inType, outType *types.Type) bool {
|
||||||
// TODO: This should maybe check for actual assignability between the two
|
// TODO: This should maybe check for actual assignability between the two
|
||||||
// types, rather than superficial traits that happen to indicate it is
|
// types, rather than superficial traits that happen to indicate it is
|
||||||
|
@ -100,6 +100,15 @@ func main() {
|
|||||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
|
|
||||||
|
// k8s.io/apimachinery/pkg/runtime contains a number of manual conversions,
|
||||||
|
// that we need to generate conversions.
|
||||||
|
// Packages being dependencies of explicitly requested packages are only
|
||||||
|
// partially scanned - only types explicitly used are being traversed.
|
||||||
|
// Not used functions or types are omitted.
|
||||||
|
// Adding this explicitly to InputDirs ensures that the package is fully
|
||||||
|
// scanned and all functions are parsed and processed.
|
||||||
|
genericArgs.InputDirs = append(genericArgs.InputDirs, "k8s.io/apimachinery/pkg/runtime")
|
||||||
|
|
||||||
if err := generatorargs.Validate(genericArgs); err != nil {
|
if err := generatorargs.Validate(genericArgs); err != nil {
|
||||||
klog.Fatalf("Error: %v", err)
|
klog.Fatalf("Error: %v", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user