Skip to content

Fix nil-comparison issues: bracket access and cache invalidation - #7

Open
gillisd wants to merge 2 commits into
masterfrom
fix/nil-comparison-issues
Open

Fix nil-comparison issues: bracket access and cache invalidation#7
gillisd wants to merge 2 commits into
masterfrom
fix/nil-comparison-issues

Conversation

@gillisd

@gillisd gillisd commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Summary

  • Flexor#[] now returns Ruby's literal nil for missing keys AND for empty Flexors already in storage, so ||=, ||, and conditional assignment behave the way Ruby developers expect. Phantom Flexors no longer leak into truthiness checks.
  • Method access (f.foo) keeps autovivifying — the documented "Safe Chaining" feature (f.user.name = "Joe") is preserved end-to-end, including when interleaved with bracket access.
  • After #delete or #clear, cached singleton getters from prior method access are now invalidated, so re-accessing a removed key vivifies a fresh child Flexor instead of returning a stale nil. FlexKeys invalidates the case counterpart as well, so deleting :user_name also clears any cached :userName getter.
  • Asymmetric contract is intentional and documented in README + docs/specification.yaml. Cached method getters resolve through FlexKeys correctly (camelCase ↔ snake_case still round-trips on the second call).

Test plan

  • bundle exec rake is fully green (377 examples, 0 failures; rubocop 0 offenses)
  • Regression: f = Flexor.new; f[:foo] ||= :bar; f[:foo]:bar
  • Regression: same after a method touch — _ = f.foo; f[:foo] ||= :bar; f[:foo]:bar
  • Method-style chain still works: f = Flexor.new; f.user.name = "Joe"; f[:user][:name]"Joe"
  • FlexKeys cached read still resolves: s = Flexor.new(fooBar: "v"); s.foo_bar; s.foo_bar"v" on both calls
  • Cache invalidation on delete: f = Flexor.new(user: { name: "a" }); f.user.name; f.delete(:user); f.user.name = "b" returns "b" (no NoMethodError on nil)
  • Cache invalidation on clear: same shape with f.clear substituted for f.delete(:user)
  • FlexKeys case-alternate invalidation: s = Flexor.new(user_name: { nickname: "a" }); s.userName.nickname; s.delete(:user_name); s.userName.nickname = "b" returns "b"
  • Documented contract change: Flexor.new[:user][:name] = "x" raises NoMethodError (use method-style chain instead)
  • Frozen-store contract: bracket on missing key returns nil silently; method-style autovivification raises FrozenError

Notes

Closes issue 3ED3C15C-4051-11F1-9F71-FE6CB9572C2F in issues.rec.

Trade-off accepted: bracket-chain assignment through an unset intermediate (f[:user][:name] = "x" when :user is unset) now raises NoMethodError. Use f.user[:name] = "x" or f.user.name = "x" instead. README and contract specs document this explicitly.

gillisd added 2 commits April 25, 2026 10:25
Note that these specs should be put in the appropriate places, this is
just a temp location so that they aren't lost
@gillisd

gillisd commented Apr 25, 2026

Copy link
Copy Markdown
Owner Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@gillisd gillisd changed the title Fix bracket-access nil contract for ||= and conditional assignment Fix nil-comparison issues: bracket access and cache invalidation Apr 26, 2026
gillisd added a commit that referenced this pull request Apr 29, 2026
Pattern matching via case/in now mirrors Core#[] — phantom keys created
by a method-touch bind to literal nil instead of leaking the empty
Flexor. Extends the bracket-nil contract from PR #7 across the
pattern-matching surface in both Core and FlexKeys.

Note: deconstruct_keys now diverges from to_h on phantom keys
(included-as-nil vs omitted), pinned in flexor_conversion_spec so the
trade-off stays discoverable.
@gillisd
gillisd force-pushed the fix/nil-comparison-issues branch from 1ad462a to 7989107 Compare June 22, 2026 16:47
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.

1 participant