mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
Explicit conversion generator
This commit is contained in:
parent
f08dfb4cb6
commit
5823fffc23
@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@ -41,6 +42,9 @@ const (
|
||||
// e.g., "+k8s:conversion-gen=false" in a type's comment will let
|
||||
// conversion-gen skip that type.
|
||||
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
|
||||
// <type-pkg> is the relative path to the package the types are defined in.
|
||||
externalTypesTagName = "k8s:conversion-gen-external-types"
|
||||
@ -50,6 +54,10 @@ func extractTag(comments []string) []string {
|
||||
return types.ExtractCommentTags("+", comments)[tagName]
|
||||
}
|
||||
|
||||
func extractExplicitFromTag(comments []string) []string {
|
||||
return types.ExtractCommentTags("+", comments)[explicitFromTagName]
|
||||
}
|
||||
|
||||
func extractExternalTypesTag(comments []string) []string {
|
||||
return types.ExtractCommentTags("+", comments)[externalTypesTagName]
|
||||
}
|
||||
@ -240,14 +248,22 @@ func Packages(context *generator.Context, arguments *args.GeneratorArgs) generat
|
||||
peerPkgs := extractTag(pkg.Comments)
|
||||
if peerPkgs != nil {
|
||||
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 {
|
||||
klog.V(5).Infof(" no tag")
|
||||
continue
|
||||
}
|
||||
skipUnsafe := false
|
||||
if customArgs, ok := arguments.CustomArgs.(*conversionargs.CustomArgs); ok {
|
||||
peerPkgs = append(peerPkgs, customArgs.BasePeerDirs...)
|
||||
peerPkgs = append(peerPkgs, customArgs.ExtraPeerDirs...)
|
||||
if len(peerPkgs) > 0 {
|
||||
peerPkgs = append(peerPkgs, customArgs.BasePeerDirs...)
|
||||
peerPkgs = append(peerPkgs, customArgs.ExtraPeerDirs...)
|
||||
}
|
||||
skipUnsafe = customArgs.SkipUnsafe
|
||||
}
|
||||
|
||||
@ -457,12 +473,13 @@ type genConversion struct {
|
||||
// the package that the conversion funcs are going to be output to
|
||||
outputPackage string
|
||||
// packages that contain the peer of types in typesPacakge
|
||||
peerPackages []string
|
||||
manualConversions conversionFuncMap
|
||||
imports namer.ImportTracker
|
||||
types []*types.Type
|
||||
skippedFields map[*types.Type][]string
|
||||
useUnsafe TypesEqual
|
||||
peerPackages []string
|
||||
manualConversions conversionFuncMap
|
||||
imports namer.ImportTracker
|
||||
types []*types.Type
|
||||
explicitConversions []conversionPair
|
||||
skippedFields map[*types.Type][]string
|
||||
useUnsafe TypesEqual
|
||||
}
|
||||
|
||||
func NewGenConversion(sanitizedName, typesPackage, outputPackage string, manualConversions conversionFuncMap, peerPkgs []string, useUnsafe TypesEqual) generator.Generator {
|
||||
@ -470,14 +487,15 @@ func NewGenConversion(sanitizedName, typesPackage, outputPackage string, manualC
|
||||
DefaultGen: generator.DefaultGen{
|
||||
OptionalName: sanitizedName,
|
||||
},
|
||||
typesPackage: typesPackage,
|
||||
outputPackage: outputPackage,
|
||||
peerPackages: peerPkgs,
|
||||
manualConversions: manualConversions,
|
||||
imports: generator.NewImportTracker(),
|
||||
types: []*types.Type{},
|
||||
skippedFields: map[*types.Type][]string{},
|
||||
useUnsafe: useUnsafe,
|
||||
typesPackage: typesPackage,
|
||||
outputPackage: outputPackage,
|
||||
peerPackages: peerPkgs,
|
||||
manualConversions: manualConversions,
|
||||
imports: generator.NewImportTracker(),
|
||||
types: []*types.Type{},
|
||||
explicitConversions: []conversionPair{},
|
||||
skippedFields: map[*types.Type][]string{},
|
||||
useUnsafe: useUnsafe,
|
||||
}
|
||||
}
|
||||
|
||||
@ -534,17 +552,55 @@ func (g *genConversion) convertibleOnlyWithinPackage(inType, outType *types.Type
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *genConversion) Filter(c *generator.Context, t *types.Type) bool {
|
||||
peerType := getPeerTypeFor(c, t, g.peerPackages)
|
||||
if peerType == nil {
|
||||
return false
|
||||
}
|
||||
if !g.convertibleOnlyWithinPackage(t, peerType) {
|
||||
return false
|
||||
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
|
||||
}
|
||||
|
||||
g.types = append(g.types, t)
|
||||
return true
|
||||
func (g *genConversion) Filter(c *generator.Context, t *types.Type) bool {
|
||||
convertibleWithPeer := func() bool {
|
||||
peerType := getPeerTypeFor(c, t, g.peerPackages)
|
||||
if peerType == nil {
|
||||
return false
|
||||
}
|
||||
if !g.convertibleOnlyWithinPackage(t, peerType) {
|
||||
return false
|
||||
}
|
||||
g.types = append(g.types, t)
|
||||
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 {
|
||||
@ -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"))
|
||||
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
|
||||
for pair, t := range g.manualConversions {
|
||||
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 {
|
||||
klog.V(5).Infof("generating for type %v", t)
|
||||
peerType := getPeerTypeFor(c, t, g.peerPackages)
|
||||
sw := generator.NewSnippetWriter(w, c, "$", "$")
|
||||
g.generateConversion(t, peerType, sw)
|
||||
g.generateConversion(peerType, t, sw)
|
||||
|
||||
if peerType := getPeerTypeFor(c, t, g.peerPackages); peerType != nil {
|
||||
g.generateConversion(t, peerType, 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()
|
||||
}
|
||||
|
||||
@ -972,6 +1056,124 @@ func (g *genConversion) doUnknown(inType, outType *types.Type, sw *generator.Sni
|
||||
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 {
|
||||
// TODO: This should maybe check for actual assignability between the two
|
||||
// types, rather than superficial traits that happen to indicate it is
|
||||
|
@ -100,6 +100,15 @@ func main() {
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
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 {
|
||||
klog.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user