Skip to content

fix: avoid uncompilable != zero check for non-comparable structs (#227) - #228

Closed
chiliec wants to merge 1 commit into
jmattheis:mainfrom
chiliec:fix/noncomparable-zero-value-check
Closed

fix: avoid uncompilable != zero check for non-comparable structs (#227)#228
chiliec wants to merge 1 commit into
jmattheis:mainfrom
chiliec:fix/noncomparable-zero-value-check

Conversation

@chiliec

@chiliec chiliec commented Aug 18, 2026

Copy link
Copy Markdown

What

Closes #227.

update:ignoreZeroValueField decides whether to skip a field by comparing the source against its zero value with !=:

if source.Inner != (execution.Inner{}) {
    target.Inner = c.convertInner(source.Inner)
}

When the source field is a struct that contains a slice, map or function, that struct is not comparable, and Go rejects != on it — so the generated code does not compile:

invalid operation: source.Inner != (Inner{}) (struct containing []string cannot be compared)

(The maintainer confirmed in the issue that goverter should never emit non-compiling code.)

Fix

Added xtype.NotZeroValueCheck(sourceCode, type), used at both zero-check emission sites in builder/struct.go. It:

  • keeps source != <zero> for comparable types, and for types whose zero value is nil (pointers, slices, maps, channels, funcs, interfaces) — so != nil behaviour is unchanged;
  • falls back to !reflect.ValueOf(source).IsZero() only for non-comparable structs/arrays, which is valid for any type.

Generated output for the issue's case becomes:

import "reflect"
...
if !reflect.ValueOf(source.Inner).IsZero() {
    target.Inner = c.convertInner(source.Inner)
}

The reflect import is added automatically by the code generator only when the fallback is used.

Tests

Added scenario/update_ignore_zero_value_noncomparable_struct.yml covering a non-comparable struct field in update mode.

Verified RED→GREEN: with the old != codegen restored, the scenario generates source.Inner != (execution.Inner{}) and fails; I also confirmed that exact line does not compile (struct containing []string cannot be compared). With the fix it generates the reflect.ValueOf(...).IsZero() form, which compiles.

Validation (real results, Go 1.25)

  • go test ./...: all pass — the full scenario suite is green, and no existing golden files changed (maps/slices still use != nil; only genuinely non-comparable structs switch to the reflect form).
  • go build ./..., gofmt -l, go vet ./builder/ ./xtype/: clean.

update:ignoreZeroValueField generated `source != ZeroValue` to decide
whether to skip a field. When the source is a struct containing a slice,
map or function, that struct is not comparable and Go rejects `!=`,
producing generated code that does not compile (jmattheis#227).

Add xtype.NotZeroValueCheck: it keeps `!= <zero>` for comparable types
and for types whose zero value is nil (pointers, slices, maps, channels,
funcs, interfaces), and falls back to !reflect.ValueOf(x).IsZero() only
for non-comparable structs and arrays. Both zero-check emission sites in
builder/struct.go now use it.

Closes jmattheis#227.
Comment thread xtype/zero.go
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.

@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.98%. Comparing base (8ee6711) to head (325d632).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #228      +/-   ##
==========================================
+ Coverage   95.96%   95.98%   +0.01%     
==========================================
  Files          50       50              
  Lines        2754     2765      +11     
==========================================
+ Hits         2643     2654      +11     
  Misses         80       80              
  Partials       31       31              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chiliec

chiliec commented Aug 26, 2026

Copy link
Copy Markdown
Author

Thanks for the review @jmattheis, and fair point — a runtime reflection check contradicts the "generated code contains no reflection" guarantee, so this approach is wrong for goverter.

I'm happy to redo it along the lines you described in #227: make goverter gen emit an error when a field is non-comparable (so it has to be explicitly goverter:ignored), instead of the reflection-based zero check. Want me to take a shot at that here, or would you rather implement the codegen-error + goverter:after design yourself? Whatever's least friction for you — just let me know and I'll either rework this PR or close it.

@jmattheis

Copy link
Copy Markdown
Owner

I'd rather do this myself, tho it will take some time as I'm currently busy.

@jmattheis jmattheis closed this Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

update:ignoreZeroValueField generates uncompilable != zero check for non-comparable struct fields

2 participants