Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions builder/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (s *Struct) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, s
return nil, err.Lift(lift...)
}
if shouldCheckAgainstZero(ctx, nextSource, targetFieldType, assignTo.Update, false) {
stmt = append(stmt, jen.If(nextID.Code.Clone().Op("!=").Add(xtype.ZeroValue(nextSource.T))).Block(fieldStmt...))
stmt = append(stmt, jen.If(xtype.NotZeroValueCheck(nextID.Code, nextSource.T)).Block(fieldStmt...))
} else {
stmt = append(stmt, fieldStmt...)
}
Expand Down Expand Up @@ -131,7 +131,7 @@ func (s *Struct) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, s
callStmt = append(callStmt, assignTo.Stmt.Clone().Dot(targetField.Name()).Op("=").Add(callReturnID.Code))

if shouldCheckAgainstZero(ctx, functionCallSourceType, targetFieldType, assignTo.Update, true) {
stmt = append(stmt, jen.If(functionCallSourceID.Code.Clone().Op("!=").Add(xtype.ZeroValue(functionCallSourceType.T))).Block(callStmt...))
stmt = append(stmt, jen.If(xtype.NotZeroValueCheck(functionCallSourceID.Code, functionCallSourceType.T)).Block(callStmt...))
} else {
stmt = append(stmt, callStmt...)
}
Expand Down
60 changes: 60 additions & 0 deletions scenario/update_ignore_zero_value_noncomparable_struct.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
input:
input.go: |
package execution

// Inner is not comparable (it contains a slice), so `inner != Inner{}`
// does not compile. goverter must use reflect.ValueOf(...).IsZero() when
// skipping zero values for such a field (#227).
type Inner struct {
Items []string
}

type Source struct {
Inner Inner
}

type Target struct {
Inner Inner
}

// goverter:converter
// goverter:default:update
// goverter:update:ignoreZeroValueField
type testConverter interface {
// goverter:default NewTarget
ToTarget(source Source) Target
}

func NewTarget() Target {
return Target{}
}
success:
- generated/generated.go: |
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.

package generated

import (
execution "github.com/jmattheis/goverter/execution"
"reflect"
)

type testConverterImpl struct{}

func (c *testConverterImpl) ToTarget(source execution.Source) execution.Target {
executionTarget := execution.NewTarget()
if !reflect.ValueOf(source.Inner).IsZero() {
executionTarget.Inner = c.executionInnerToExecutionInner(source.Inner)
}
return executionTarget
}
func (c *testConverterImpl) executionInnerToExecutionInner(source execution.Inner) execution.Inner {
var executionInner execution.Inner
if source.Items != nil {
executionInner.Items = make([]string, len(source.Items))
for i := 0; i < len(source.Items); i++ {
executionInner.Items[i] = source.Items[i]
}
}
return executionInner
}
29 changes: 29 additions & 0 deletions xtype/zero.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,35 @@ import (
"github.com/dave/jennifer/jen"
)

// NotZeroValueCheck returns an expression that is true when sourceCode does not
// hold the zero value of type t.
//
// For most types this is `sourceCode != <zero>` (where <zero> is `nil` for
// pointers, slices, maps, channels, functions and interfaces). Some struct and
// array types are not comparable because they contain a slice, map or function,
// and Go rejects `!=` on them, producing uncompilable generated code (see
// #227). For those, fall back to `!reflect.ValueOf(sourceCode).IsZero()`, which
// works for any type.
func NotZeroValueCheck(sourceCode *jen.Statement, t types.Type) *jen.Statement {
if zeroValueIsNil(t) || types.Comparable(t) {
return sourceCode.Clone().Op("!=").Add(ZeroValue(t))
}
return jen.Op("!").Add(
jen.Qual("reflect", "ValueOf").Call(sourceCode.Clone()).Dot("IsZero").Call(),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I like the reflection usage here. In my #227 (comment), I said I'd prefer that "goverter gen" errors. In our documentation it's currently listed that the generated code won't include any reflection, adding this would void this.

)
}

// zeroValueIsNil reports whether the zero value of t is nil, i.e. t can be
// compared against nil regardless of whether it is otherwise comparable
// (slices and maps are not comparable but their zero value is nil).
func zeroValueIsNil(t types.Type) bool {
switch t.Underlying().(type) {
case *types.Pointer, *types.Slice, *types.Map, *types.Chan, *types.Signature, *types.Interface:
return true
}
return false
}

func ZeroValue(t types.Type) *jen.Statement {
switch cast := t.(type) {
case *types.Basic:
Expand Down
Loading