Repository navigation
generator: make generated Go getters tolerate a nil receiver - #246
Conversation
The accessors added in crossplane#160 return their field directly, so walking nested getters panics as soon as an intermediate struct is nil: cd.GetStatus().GetProviderConfigRefs().GetAws().GetName() Every field of a generated model is optional, so intermediate nils are the normal case for a partially-populated resource, not an edge case. Callers therefore have to fall back to explicit nil checks at each hop, which is what the accessors were meant to avoid. Guard each getter with a nil-receiver check, as protobuf-generated getters do, so a chain over absent fields yields the zero value instead of panicking. Nilable field types return nil directly; anything else (a value field, a fixed-size array) returns a declared zero value. Setters are deliberately left unguarded: a set on a nil receiver has nowhere to store the value, so panicking is the honest behaviour. Getter signatures are unchanged, and code that worked before still works -- this only widens the set of receivers a getter accepts. Signed-off-by: Erik Miller <erik.miller@gusto.com>
📝 WalkthroughWalkthroughGenerated getters now guard nil receivers and return type-appropriate zero values. Tests verify generated guard structure, preserve unguarded setters, and confirm safe chained access through nil intermediate values. ChangesNil-safe accessor generation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant TestGeneratedModels
participant ChainOnEmpty
participant GeneratedGetter
TestGeneratedModels->>ChainOnEmpty: Execute chained getter test
ChainOnEmpty->>GeneratedGetter: Call getters on nil intermediate values
GeneratedGetter-->>ChainOnEmpty: Return nil without panic
Suggested reviewers: 🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
internal/schemas/generator/accessors_test.go (1)
419-438: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse table cases that assert the complete nil branch.
Thanks for adding coverage for nil receivers. Convert these checks into cases with
reason,args, andwantfields. Includereturn zeroin the expected scalar and fixed-array branches. The current checks pass if a getter declareszerobut returns a different value.As per path instructions,
**/*_test.gorequires a table-driven test structure withargs/want, case naming, andreasonfields.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/schemas/generator/accessors_test.go` around lines 419 - 438, The nil-receiver assertions in the accessor test should become table-driven cases with case names plus reason, args, and want fields. Update the cases for GetBar, GetCount, and GetFixed to compare the complete generated nil branch, explicitly requiring return nil for the nilable field and var zero followed by return zero for scalar and fixed-array fields; retain the SetBar guard assertion.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@internal/schemas/generator/accessors_test.go`:
- Around line 419-438: The nil-receiver assertions in the accessor test should
become table-driven cases with case names plus reason, args, and want fields.
Update the cases for GetBar, GetCount, and GetFixed to compare the complete
generated nil branch, explicitly requiring return nil for the nilable field and
var zero followed by return zero for scalar and fixed-array fields; retain the
SetBar guard assertion.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 44608a88-35aa-4e42-b63b-f5081f06005b
📒 Files selected for processing (2)
internal/schemas/generator/accessors.gointernal/schemas/generator/accessors_test.go
adamwg
left a comment
There was a problem hiding this comment.
One question, but assuming my assumption is correct, this lgtm. This will definitely help the ergonomics of the Go bindings 🙏
| b.WriteString("\n// Get" + fieldName + " returns the " + fieldName + " field.\n") | ||
| b.WriteString("// It returns the zero value if the receiver is nil.\n") | ||
| b.WriteString("func (" + accessorReceiver + " *" + typeName + ") Get" + fieldName + "() " + fieldType + " {\n") | ||
| b.WriteString("\tif " + accessorReceiver + " == nil {\n") |
There was a problem hiding this comment.
Are the receivers guaranteed to be nil-able? I think they are because we mark every openapi field as optional, so they all become pointers - is that right?
crossplane 2.5.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>## What's Changed * renovate: Add release-2.4 to the base branches by @adamwg in crossplane/cli#144 * validate: Use crossplane:stable as the default image by @adamwg in crossplane/cli#148 * chore(deps): update module github.com/sigstore/cosign/v2 to v2.6.3 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#150 * chore(deps): update module github.com/sigstore/rekor to v1.5.2 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#152 * fix(deps): update module github.com/docker/cli to v29.6.0+incompatible (main) by @crossplane-renovate[bot] in crossplane/cli#146 * fix(deps): update module github.com/emicklei/dot to v1.11.0 (main) by @crossplane-renovate[bot] in crossplane/cli#147 * fix(render): annotate functions when reusing an existing network by @jcogilvie in crossplane/cli#159 * render: Have the context function listen on a TCP port by @adamwg in crossplane/cli#163 * chore(deps): update module github.com/sigstore/cosign/v3 to v3.0.6 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#151 * fix(deps): update module github.com/docker/cli to v29.6.1+incompatible (main) by @crossplane-renovate[bot] in crossplane/cli#164 * chore(deps): update module oras.land/oras-go/v2 to v2.6.1 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#178 * chore(deps): update renovatebot/github-action action to v46.1.18 (main) by @crossplane-renovate[bot] in crossplane/cli#175 * projects: Correctly build multi-arch Python functions by @adamwg in crossplane/cli#192 * chore(deps): update module oras.land/oras-go/v2 to v2.6.2 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#189 * projects: Skip empty documents when applying init and extra resources by @ytsarev in crossplane/cli#208 * chore(deps): update module github.com/yuin/goldmark to v1.7.17 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#183 * fix(deps): update module github.com/crossplane/crossplane/apis/v2 to v2.3.4 (main) by @crossplane-renovate[bot] in crossplane/cli#209 * fix(deps): update module google.golang.org/grpc to v1.82.1 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#201 * Fix two potential race conditions in project-related code by @adamwg in crossplane/cli#171 * feat: add --json-schema flag to xrd convert by @fernandezcuesta in crossplane/cli#142 * validate: Recognize Kubernetes built-in resources by @ihopenre-eng in crossplane/cli#197 * chore(deps): update actions/checkout digest to d23441a (main) by @crossplane-renovate[bot] in crossplane/cli#217 * chore(deps): update actions/stale digest to 1e223db (main) by @crossplane-renovate[bot] in crossplane/cli#218 * fix(deps): update module github.com/getkin/kin-openapi to v0.144.0 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#222 * chore(deps): update module github.com/google/cel-go to v0.29.0 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#220 * chore(deps): update module github.com/go-chi/chi/v5 to v5.3.0 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#219 * chore(deps): update module github.com/sigstore/timestamp-authority/v2 to v2.1.0 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#174 * chore(deps): update module github.com/klauspost/compress to v1.18.7 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#227 * chore(deps): update cachix/install-nix-action digest to 630ae54 (main) by @crossplane-renovate[bot] in crossplane/cli#228 * build: Calculate checksums after the nix fixup phase by @adamwg in crossplane/cli#235 * chore(deps): update module github.com/sigstore/sigstore-go to v1.2.1 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#186 * chore(deps): update github/codeql-action digest to f205ea1 (main) by @crossplane-renovate[bot] in crossplane/cli#232 * fix(deps): update module github.com/docker/cli to v29.7.1+incompatible (main) by @crossplane-renovate[bot] in crossplane/cli#234 * chore(deps): update renovatebot/github-action action to v46.2.0 (main) by @crossplane-renovate[bot] in crossplane/cli#233 * feat: Allow custom type declaration in SimpleSchema by @BigGold1310 in crossplane/cli#238 * schemas: Indent .lock.json for readability by @Bham06 in crossplane/cli#231 * fix(common/load/streamToUnstructured): skip empty documents by @nkzk in crossplane/cli#241 * fix(deps): update module github.com/go-git/go-billy/v5 to v5.9.1 (main) by @crossplane-renovate[bot] in crossplane/cli#240 * chore(deps): update renovatebot/github-action action to v46.2.1 (main) by @crossplane-renovate[bot] in crossplane/cli#239 * feat: Add --replace flag to crossplane xrd generate by @BigGold1310 in crossplane/cli#243 * feat: add GetX/SetX accessors to generated Go models by @erikmiller-gusto in crossplane/cli#160 * generator: make generated Go types implement runtime.Object and provide AddToScheme by @erikmiller-gusto in crossplane/cli#162 * generator: make generated Go getters tolerate a nil receiver by @erikmiller-gusto in crossplane/cli#246 * chore(deps): update github/codeql-action digest to 5595cca (main) by @crossplane-renovate[bot] in crossplane/cli#244 * fix(deps): update module github.com/go-git/go-git/v5 to v5.19.2 (main) by @crossplane-renovate[bot] in crossplane/cli#245 * ci: fix Renovate's Nix updates and bump nixpkgs to nixos-26.05 by @jbw976 in crossplane/cli#257 * chore(deps): update renovatebot/github-action action to v46.2.2 (main) by @crossplane-renovate[bot] in crossplane/cli#261 * fix(deps): update module google.golang.org/protobuf to v1.36.12 (main) by @crossplane-renovate[bot] in crossplane/cli#262 * chore(deps): update dependency renovate to v44.23.3 (main) by @crossplane-renovate[bot] in crossplane/cli#260 * chore(deps): lock file maintenance (main) by @crossplane-renovate[bot] in crossplane/cli#258 * fix(deps): update module github.com/alecthomas/kong to v1.16.1 (main) by @crossplane-renovate[bot] in crossplane/cli#268 * chore(deps): update dependency renovate to v44.27.0 (main) by @crossplane-renovate[bot] in crossplane/cli#267 * chore(deps): update gomod2nix digest to 1201ddd (main) by @crossplane-renovate[bot] in crossplane/cli#259 * chore(deps): update korthout/backport-action action to v4.6.0 (main) by @crossplane-renovate[bot] in crossplane/cli#250 * chore(deps): update actions/checkout action to v6.1.0 (main) by @crossplane-renovate[bot] in crossplane/cli#249 * fix(ci): clean /homeless-shelter before Renovate's Nix commands by @jbw976 in crossplane/cli#279 * fix(deps): update module golang.org/x/mod to v0.40.0 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#271 * fix(schemas): fix Go model generation for validation-only combinators and shared k8s package collisions by @haarchri in crossplane/cli#269 * chore(deps): lock file maintenance (main) by @crossplane-renovate[bot] in crossplane/cli#272 * chore(deps): update cachix/install-nix-action digest to 13d8dd5 (main) by @crossplane-renovate[bot] in crossplane/cli#273 * chore(deps): update module github.com/google/cel-go to v0.30.0 [security] (main) by @crossplane-renovate[bot] in crossplane/cli#284 * fix(deps): update module github.com/getkin/kin-openapi to v0.147.0 (main) by @crossplane-renovate[bot] in crossplane/cli#280 * fix(deps): update module github.com/docker/go-connections to v0.8.1 (main) by @crossplane-renovate[bot] in crossplane/cli#276 * chore(deps): update dependency renovate to v44.33.2 (main) by @crossplane-renovate[bot] in crossplane/cli#275 * chore(deps): update github/codeql-action digest to ff2f1c6 (main) by @crossplane-renovate[bot] in crossplane/cli#274 * fix(dependency): generate schemas for transitive package dependencies by @haarchri in crossplane/cli#270 * fix(deps): update module github.com/google/go-containerregistry to v0.21.9 (main) by @crossplane-renovate[bot] in crossplane/cli#247 * chore(deps): lock file maintenance (main) by @crossplane-renovate[bot] in crossplane/cli#287 ## New Contributors * @ytsarev made their first contribution in crossplane/cli#208 * @ihopenre-eng made their first contribution in crossplane/cli#197 * @BigGold1310 made their first contribution in https:/ See merge request: Harmonybrew/homebrew-core!17385
Description of your changes
Follow-up to #160.
Problem
The generated accessors return their field directly:
So walking nested getters panics as soon as an intermediate struct is nil:
goRemoveRequiredmakes every field of a generated model optional, so intermediatenils are the normal case for a partially-populated resource, not an edge case — a
status subresource that the controller hasn't filled in yet hits this immediately.
Callers end up writing explicit nil checks at every hop, which is most of what the
accessors were meant to remove.
What this does
Guards each getter with a nil-receiver check, the way protobuf-generated getters do,
so a chain over absent fields yields the zero value instead of panicking:
Nilable field types return
nildirectly. Anything else — a value field, a fixed-sizearray — returns a declared zero value, so the guard is correct for field shapes the
generator doesn't currently emit but could.
Setters are deliberately left unguarded: a set on a nil receiver has nowhere to store
the value, so panicking is the honest behaviour rather than silently dropping a write.
What this does not do
Scalar getters keep returning pointers (
GetName() *string, notstring). Protobufreturns values for scalars, but for Kubernetes APIs the nil-vs-empty distinction is
load-bearing —
omitempty, patch semantics, and "unset" versus "explicitly empty" areall observable. Collapsing that would lose information, and the pointer return keeps
GetX/SetXsymmetric. Callers still need one nil check at the end of a chain, not oneper hop.
Compatibility
Getter signatures are unchanged, and this only widens the set of receivers a getter
accepts, so no code that works today breaks. #160 is also not in a release yet, so
there are no released consumers either way.
Testing
TestAddAccessorsGuardsNilReceiver— asserts every getter opens with the nil guard,that setters do not, and that nilable fields return
nilwhile a value field and afixed-size array get a declared zero.
getters over an empty resource, and the materialized module is
go tested. Compilingalone would not have caught this, since the old code typechecks fine and only fails at
runtime.
a nil pointer dereference, which is the bug being fixed.
confirmed
cd.GetStatus().GetProviderConfigRefs().GetAws().GetName()returns nil on anempty resource instead of panicking.
go test ./...,go vet ./...,go build ./...andgofmt -l .are all clean.I have:
Run./nix.sh flake checkto ensure this PR is ready for review.Linked a PR or a docs tracking issue to document this change.Addedbackport release-x.ylabels to auto-backport this PR.On the struck items:
nixisn't available in the environment this was developed in, soflake checkhasn't been run —go test/go vet/gofmtwere run instead, and I'dappreciate CI or a reviewer confirming the flake. This changes the body of generated
methods rather than any user-facing command or help text, so there's nothing for
crossplane/docsto pick up. And it fixes an unreleased feature from #160, so it isn't abackport candidate.
Need help with this checklist? See the cheat sheet.