Skip to content

Inferred actions (AC-1b), HELD_WRITE (AC-1c), STALE_OWN_READ (AC-1d) - #17

Merged
ryansolid merged 1 commit into
mainfrom
claude/project-thread-dnznpz
Oct 28, 2026
Merged

ryansolid merged 1 commit into
mainfrom
claude/project-thread-dnznpz

Conversation

@ryansolid

Copy link
Copy Markdown
Owner

Requested by Ryan · project thread

Ruled 2026-10-28 in the thread. Design doc: https://claude.ai/artifact/8Lv3dy6NY3cwwgy7U2B2Fj

What changes

  • AC-1b, inferred action. An arrow in imperative scope, including an event handler, is an action when its own statements contain optimistic, affects, or both a write to a source and a statement-level await. It lowers to Solid's action() the same way action { … } does, so nothing is added at run time. An effect's apply block is never inferred, because its return value is the cleanup (EF-5). A guess there stays OPTIMISTIC_OUTSIDE_HOLD, and the message now says to declare an action.
  • AC-1c, HELD_WRITE. Only for inferred actions: a plain write that reaches an await is an error. The fix depends on the write's purpose: optimistic for ephemeral state, onFlush for a reset, or a declared action to hold the write on purpose. Declared actions are not checked, because holding a write is what a transition is for (S-solid-20).
  • AC-1d, STALE_OWN_READ. In every imperative run, reading a source the same change has already written is an error, matched key by key. The read would get the committed value (W-4). I confirmed in Solid 2.0.0-rc.19 that action() behaves the same way after a yield.
  • Both checks follow calls into local closures (let f = () => { … }; f()).
  • Shared analysis: ref/src/infer.ts, ported to compiler/src/infer.rs. Wired into the checker, interpreter, ref lowering, oracle, and crate check and emit.
  • Spec: 02 (SC-4, W-3, W-4), 05 H-11, 06 (AC-1, AC-1b, AC-1c, AC-1d, AC-3, AC-5, diagnostics), 07 §5 and decision 17, 11, and 99. The guide's §3 and §5 are updated too.
  • New scenarios S-infer-1 to S-infer-5. S-check-1's row handler read hits right after writing it, which was stale, so it now uses a local. S-solid-32's rejected handler guess now compiles, since the handler is inferred as an action, so it is a frames variant instead.

Checks (Solid next @ d2323b0, rc.19)

  • interpreter, oracle, compiled (ref and RX_CRATE=1): 104/104
  • ssr: 97 pass, 7 skip
  • types: 143/143
  • crate: 303/303 agree; --lower 175/175
  • cargo test: pass

Not done: the language server doesn't show "inferred action" on hover yet, because hover comes from TypeScript over the projection.


Generated by Claude Code

An arrow in imperative scope is an action when its own statements guess,
declare `affects`, or both write a source and await. The compiler sees
every suspension (await is a statement), so it lowers such an arrow to
Solid's action() exactly as `action { … }` lowers; nothing is added at
run time. An effect's apply block is never inferred (its return is the
cleanup, EF-5).

Two static checks keep the hold visible, stricter than Solid, which
cannot see a body:
- HELD_WRITE: in an inferred action, a plain write that reaches an await
  would be held; make it optimistic (ephemeral), onFlush (a reset), or
  declare the action to hold it on purpose.
- STALE_OWN_READ: in any run, reading a source the run's own change wrote
  gets the committed value (W-4); Solid rc.19 agrees inside action().

Both follow calls into local closures. Shared analysis in ref/src/infer.ts,
ported to compiler/src/infer.rs. Scenarios S-infer-1..5; S-check-1's row
handler had a stale read; S-solid-32's handler guess is now a variant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWKaPajtqtwB6YqA277
@ryansolid ryansolid self-assigned this Oct 28, 2026
@ryansolid
ryansolid merged commit e25b81b into main Oct 28, 2026
14 checks passed
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.

2 participants