in cmd/conversion-gen/generators/conversion.go [885:1037]
func (g *genConversion) doStruct(inType, outType *types.Type, sw *generator.SnippetWriter) {
for _, inMember := range inType.Members {
if tagvals := extractTag(inMember.CommentLines); tagvals != nil && tagvals[0] == "false" {
// This field is excluded from conversion.
sw.Do("// INFO: in."+inMember.Name+" opted out of conversion generation\n", nil)
continue
}
outMember, found := findMember(outType, inMember.Name)
if !found {
// This field doesn't exist in the peer.
sw.Do("// WARNING: in."+inMember.Name+" requires manual conversion: does not exist in peer-type\n", nil)
g.skippedFields[inType] = append(g.skippedFields[inType], inMember.Name)
continue
}
inMemberType, outMemberType := inMember.Type, outMember.Type
// create a copy of both underlying types but give them the top level alias name (since aliases
// are assignable)
if underlying := unwrapAlias(inMemberType); underlying != inMemberType {
copied := *underlying
copied.Name = inMemberType.Name
inMemberType = &copied
}
if underlying := unwrapAlias(outMemberType); underlying != outMemberType {
copied := *underlying
copied.Name = outMemberType.Name
outMemberType = &copied
}
args := argsFromType(inMemberType, outMemberType).With("name", inMember.Name)
// try a direct memory copy for any type that has exactly equivalent values
if g.useUnsafe.Equal(inMemberType, outMemberType) {
args = args.
With("Pointer", types.Ref("unsafe", "Pointer")).
With("SliceHeader", types.Ref("reflect", "SliceHeader"))
switch inMemberType.Kind {
case types.Pointer:
sw.Do("out.$.name$ = ($.outType|raw$)($.Pointer|raw$(in.$.name$))\n", args)
continue
case types.Map:
sw.Do("out.$.name$ = *(*$.outType|raw$)($.Pointer|raw$(&in.$.name$))\n", args)
continue
case types.Slice:
sw.Do("out.$.name$ = *(*$.outType|raw$)($.Pointer|raw$(&in.$.name$))\n", args)
continue
}
}
// check based on the top level name, not the underlying names
if function, ok := g.preexists(inMember.Type, outMember.Type); ok {
if isDrop(function.CommentLines) {
continue
}
// copy-only functions that are directly assignable can be inlined instead of invoked.
// As an example, conversion functions exist that allow types with private fields to be
// correctly copied between types. These functions are equivalent to a memory assignment,
// and are necessary for the reflection path, but should not block memory conversion.
// Convert_unversioned_Time_to_unversioned_Time is an example of this logic.
if !isCopyOnly(function.CommentLines) || !g.isFastConversion(inMemberType, outMemberType) {
args["function"] = function
sw.Do("if err := $.function|raw$(&in.$.name$, &out.$.name$, s); err != nil {\n", args)
sw.Do("return err\n", nil)
sw.Do("}\n", nil)
continue
}
klog.V(5).Infof("Skipped function %s because it is copy-only and we can use direct assignment", function.Name)
}
// If we can't auto-convert, punt before we emit any code.
if inMemberType.Kind != outMemberType.Kind {
sw.Do("// WARNING: in."+inMember.Name+" requires manual conversion: inconvertible types ("+
inMemberType.String()+" vs "+outMemberType.String()+")\n", nil)
g.skippedFields[inType] = append(g.skippedFields[inType], inMember.Name)
continue
}
switch inMemberType.Kind {
case types.Builtin:
if inMemberType == outMemberType {
sw.Do("out.$.name$ = in.$.name$\n", args)
} else {
sw.Do("out.$.name$ = $.outType|raw$(in.$.name$)\n", args)
}
case types.Map, types.Slice, types.Pointer:
if g.isDirectlyAssignable(inMemberType, outMemberType) {
sw.Do("out.$.name$ = in.$.name$\n", args)
continue
}
sw.Do("if in.$.name$ != nil {\n", args)
sw.Do("in, out := &in.$.name$, &out.$.name$\n", args)
g.generateFor(inMemberType, outMemberType, sw)
sw.Do("} else {\n", nil)
sw.Do("out.$.name$ = nil\n", args)
sw.Do("}\n", nil)
case types.Struct:
if g.isDirectlyAssignable(inMemberType, outMemberType) {
sw.Do("out.$.name$ = in.$.name$\n", args)
continue
}
conversionExists := true
if g.convertibleOnlyWithinPackage(inMemberType, outMemberType) {
sw.Do("if err := "+nameTmpl+"(&in.$.name$, &out.$.name$, s); err != nil {\n", args)
} else {
args := argsFromType(inMemberType, outMemberType)
sw.Do("// FIXME: Provide conversion function to convert $.inType|raw$ to $.outType|raw$\n", args)
sw.Do("compileErrorOnMissingConversion()\n", nil)
conversionExists = false
}
if conversionExists {
sw.Do("return err\n", nil)
sw.Do("}\n", nil)
}
case types.Alias:
if isDirectlyAssignable(inMemberType, outMemberType) {
if inMemberType == outMemberType {
sw.Do("out.$.name$ = in.$.name$\n", args)
} else {
sw.Do("out.$.name$ = $.outType|raw$(in.$.name$)\n", args)
}
} else {
conversionExists := true
if g.convertibleOnlyWithinPackage(inMemberType, outMemberType) {
sw.Do("if err := "+nameTmpl+"(&in.$.name$, &out.$.name$, s); err != nil {\n", args)
} else {
args := argsFromType(inMemberType, outMemberType)
sw.Do("// FIXME: Provide conversion function to convert $.inType|raw$ to $.outType|raw$\n", args)
sw.Do("compileErrorOnMissingConversion()\n", nil)
conversionExists = false
}
if conversionExists {
sw.Do("return err\n", nil)
sw.Do("}\n", nil)
}
}
default:
conversionExists := true
if g.convertibleOnlyWithinPackage(inMemberType, outMemberType) {
sw.Do("if err := "+nameTmpl+"(&in.$.name$, &out.$.name$, s); err != nil {\n", args)
} else {
args := argsFromType(inMemberType, outMemberType)
sw.Do("// FIXME: Provide conversion function to convert $.inType|raw$ to $.outType|raw$\n", args)
sw.Do("compileErrorOnMissingConversion()\n", nil)
conversionExists = false
}
if conversionExists {
sw.Do("return err\n", nil)
sw.Do("}\n", nil)
}
}
}
}