Skip to content

Clarify direct value construction and deep snapshots - #158

Merged
TheLazyCat00 merged 11 commits into
claude/player-pointer-aliasing-frj4aefrom
agent/fix-deep-copy-snapshot-rules
Aug 8, 2026
Merged

Clarify direct value construction and deep snapshots#158
TheLazyCat00 merged 11 commits into
claude/player-pointer-aliasing-frj4aefrom
agent/fix-deep-copy-snapshot-rules

Conversation

@TheLazyCat00

@TheLazyCat00 TheLazyCat00 commented Aug 4, 2026

Copy link
Copy Markdown
Member

Authorship note: This PR was prepared by ChatGPT at @TheLazyCat00's request.

Summary

This follow-up fixes the three findings from the ChatGPT review on #155:

  • Define destination-driven construction for fresh value expressions. A non-place result constructs directly in its eventual storage, recursively through products, value-variant payloads, function results, and match arms; an existing place is copied.
  • Define overlap-safe value overwrite ordering. The right-hand side observes the pre-overwrite value, and any replacement that reads the destination is completed before the old occupant and its owned blocks are destroyed.
  • Align deep snapshots with ordinary copy semantics. Snapshot blocks belong to the fresh destination binding's scope rather than generally sharing the writer's stacks.
  • Treat optimistic snapshot bytes as untrusted. Discriminants and other structure-directing metadata are validated before typed interpretation, followed by complete-span and depth validation and the final version check.

Why

Without direct destination construction, nested fresh values such as Nat.succ(Nat.succ(...)) can be read as repeatedly copying completed prefixes, making ordinary construction quadratic. The same rule applies orthogonally to product values such as Vector2(Int(3), Int(4)).

Without explicit overlap ordering, forms such as x = x or x = x.child could destroy the source before it is copied.

The snapshot text also claimed same-stack contention as a general rule even though ordinary deep copies allocate in the destination binding's scope. Finally, a concurrent reader must validate a transient sum tag or other layout-directing metadata before using it to interpret payload bytes.

Files

  • spec/memory.md
  • spec/concurrency.md
  • spec/glossary.md
  • stories/memory.md

Validation

  • Compared the branch directly against f6f7ca50bfc7f4bea3fef7270725123f24198b92.
  • Confirmed it is eleven commits ahead and zero behind.
  • Final diff is limited to the four documentation files above.
  • Verified the glossary distinguishes copying an existing place from direct construction of a fresh non-place result.
  • Verified rejected snapshot attempts return all provisional destination allocations before retrying.
  • Verified the snapshot depth bound applies independently to every retry attempt.
  • Verified spec/memory.md ends with a single trailing newline.
  • Documentation-only change; no executable tests apply.

Summary by CodeRabbit

  • Documentation
    • Clarified deep-copy behavior for recursive values, including owned boxed data.
    • Documented direct construction of new values and complete evaluation before overwriting existing values.
    • Clarified that copying existing values preserves the full value structure.
    • Expanded memory and allocation rules for safely reclaiming replaced data.
    • Clarified snapshot validation for variants, sizes, payload boundaries, and traversal depth.
    • Documented safe handling of invalid or stale snapshots, including retries and possible starvation.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The specification clarifies recursive deep copying, direct construction of fresh values, safe overwrite ordering, and validation rules for concurrent boxed-value snapshots.

Changes

Memory and snapshot rules

Layer / File(s) Summary
Value construction and overwrite semantics
spec/memory.md, spec/glossary.md, stories/memory.md
Fresh values are constructed in their destinations. Place bindings perform recursive deep copies. Overwrites materialize replacements before reclaiming prior storage.
Boxed snapshot validation
spec/concurrency.md, stories/memory.md
Snapshot walks validate discriminants, counts, lengths, spans, depth, and versions. Failed attempts reclaim provisional allocations before retrying. Accepted payloads use the destination scope.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • zane-lang/spec#155: Covers related recursive boxed-value ownership and deep-copy semantics.

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: direct value construction and deep snapshot behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/fix-deep-copy-snapshot-rules

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@TheLazyCat00
TheLazyCat00 marked this pull request as ready for review August 4, 2026 10:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@spec/concurrency.md`:
- Line 134: Update spec/concurrency.md lines 134-134 to state that rejected
snapshot attempts must reclaim all partial-copy blocks from the destination
scope before retrying, preventing retained allocations across retries. Add the
same reclamation rule to stories/memory.md lines 225-225; both documentation
sites require direct updates.

In `@spec/memory.md`:
- Line 641: Add exactly one trailing newline at the end of spec/memory.md, after
the final “lifetimes.md” reference, without changing the documented content.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: zane-lang/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ba010d06-2051-4d49-90da-07b3aaae33ff

📥 Commits

Reviewing files that changed from the base of the PR and between f6f7ca5 and 204cb25.

📒 Files selected for processing (3)
  • spec/concurrency.md
  • spec/memory.md
  • stories/memory.md

Comment thread spec/concurrency.md Outdated
Comment thread spec/memory.md Outdated
@TheLazyCat00

Copy link
Copy Markdown
Member Author

review from ai:

PR #158 review summary

PR #158 (Clarify direct value construction and deep snapshots) makes targeted documentation-only updates across four files to align the language specification with the deep-copy semantics enabled by PR #155. The changes are internally consistent and correctly thread new terminology through the spec.

spec/memory.md

• §2.3 (Value construction): Now distinguishes two binding paths explicitly. Fresh non-place expressions construct directly into their destination, recursively through nested fresh results. Place expressions are copied whole. Overwrite ordering is formalized: the RHS observes the pre-overwrite occupant, any overlap is materialized before the old occupant is destroyed, and the destruction of the old occupant's dynamic blocks happens recursively before the replacement is installed. Recursive cases like Nat.succ avoid redundant copy costs by building directly in the final destination..
• §2.10 (A value type contains only values): Terminology updated from "A value is copied, whole, every time it is bound into a fresh slot" to "An existing value is copied whole whenever a place expression is bound into a different slot", matching §2.3..
• §3.2 (Allocation, reuse, and teardown): The first paragraph now points reference-type overwrites at §2.2 and §3.7, and value-slot overwrites at §2.3's replacement rule. The new sentence ("If the compiler proves the replacement does not depend on the current occupant, it may destroy the old value and construct a non-place result directly in that slot") is a direct consequence of destination-driven construction and is correctly placed..

spec/glossary.md

• §3.2 (value-downstream enforcement): Reworded so the "copying" rationale names the two binding paths from §2.3 (place expression is copied, fresh non-place constructs in place). Preserves the recursion exception via boxed members..
• §3.40 (deep value copy): Reframed as the definition of copying, not of all value binding. A fresh non-place expression is explicitly carved out as constructing directly in its destination, so it does not pay an extra copy. The "no boxed member" caveat is correctly retained for the inline-byte case. Cross-reference to §2.3 is correct..

spec/concurrency.md

• §4.4 (Reads take a coherent snapshot): Two notable additions and one substantial rewrite..
◦ New bullet: "Structure-directing metadata is untrusted until validation succeeds." This is a direct, well-placed response to the optimistic-snapshot concern; discriminants, counts, and lengths must be validated before they shape traversal, and an invalid discriminant aborts and triggers a retry. It correctly notes that a valid stale value is still discarded by the final version check..
◦ Bullet rewritten: "The reader allocates in the destination scope." Each boxed payload is allocated from the size stacks of the scope that owns the destination binding, not the writer's or source's. The clause about allocator synchronization only being required when concurrent work shares an underlying arena is a correct and important refinement..
◦ The "bounded walk" bullet still mandates complete-payload-span validation and a depth bound, and the new metadata bullet complements rather than duplicates it..

stories/memory.md

• Updated "What a copy is for, and the ban that survived it" to emphasize that deep copy is the standard definition of copying for values with out-of-line storage, and to reinforce that reference types (List, String) remain banned from value types because they are designed to be moved, not copied. The "Nat.succ" example continues to work as a fresh-construction case, consistent with the new §2.3..

Cross-cutting observations

• The terminology thread (non-place expression, place expression, destination-driven construction, overlap-safe, untrusted snapshot bytes) is used consistently across §2.3, §3.2, glossary §3.2/§3.40, and concurrency §4.4..
• The PR does not introduce new runtime behavior, only clarifies what PR #155 already settled. No contradictory guidance is introduced anywhere..
• One thing worth flagging for the author (not a blocker): §4.4's two bullets ("The walk MUST be bounded..." and "Structure-directing metadata is untrusted...") together cover what counts as a safe walk, but they do not explicitly say the depth bound applies across retries. Since each retry re-runs the whole walk, that is implicit, but making it explicit would help future implementers. Optional nit..

Overall, the changes are accurate, scoped, and consistent. Recommend approval, optionally with the §4.4 depth-bound nit.

@TheLazyCat00

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@spec/concurrency.md`:
- Around line 133-134: Update the bounded-walk and retry wording in
spec/concurrency.md lines 133-134 so each snapshot retry starts a new walk with
the same per-attempt depth limit. Mirror this per-attempt depth-limit
requirement in stories/memory.md line 225; both sites require direct
documentation updates.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: zane-lang/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dccc7074-4869-41cf-994b-aba8ce7860e2

📥 Commits

Reviewing files that changed from the base of the PR and between 204cb25 and cf670d1.

📒 Files selected for processing (4)
  • spec/concurrency.md
  • spec/glossary.md
  • spec/memory.md
  • stories/memory.md

Comment thread spec/concurrency.md Outdated
@TheLazyCat00
TheLazyCat00 merged commit 2299fe8 into claude/player-pointer-aliasing-frj4ae Aug 8, 2026
1 check passed
@TheLazyCat00
TheLazyCat00 deleted the agent/fix-deep-copy-snapshot-rules branch August 8, 2026 10:31
TheLazyCat00 added a commit that referenced this pull request Aug 8, 2026
Review asked directly why a recursive variant must box, given that every
value bottoms out at a leaf. The spec only asserted that an inline
self-reference "would have infinite size", which does not answer it.

adt.md §4 now separates the two things being conflated. A recursive type's
*values* are finite; layout is fixed per type and settled before any value
exists, so the question is what one stride must be for every value at once.
`size(Nat) = tag + size(Nat)` has no finite solution, and no stride can be
taken from the deepest value because there is no deepest value — one `Nat`
holds three nodes and the next three million, and uniform stride requires
both to be the same size. The leaf terminates a value, not the type's size
equation.

Also reconciles two passages that #158 left describing the pre-#158 copy
rule, now that a fresh non-place result constructs directly in its
destination:

- adt.md §4 said a recursive value type walks and reallocates on *every*
  store into fresh storage. That is now true only for binding an existing
  place; `Nat.succ(Nat.succ(...))` builds each node once where it will live.
- memory.md §6 said a value parameter is "copied when bound into a fresh
  slot", which reads as though passing were the copy. The copy happens when
  the parameter, an existing place, is itself bound onward.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017AYiJXjzCPEDfYRW1ZtxH7
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