Skip to content

Rust crate: front half, resolved model (CP-6), lowering through compile_program, and @reactivescript/compiler - #6

Merged
ryansolid merged 12 commits into
mainfrom
claude/project-thread-axchtp
Oct 27, 2026
Merged

ryansolid merged 12 commits into
mainfrom
claude/project-thread-axchtp

Conversation

@ryansolid

@ryansolid ryansolid commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Requested by Ryan · project thread

This PR is the Rust port as agreed in #1. It covers:

What's here

  • compiler/: the reactivescript crate, with the binary rxc. It ports ref/src/{parse,ast,modules,content,signature,check}.ts: parsing, the module graph, every diagnostic with the same message, and signatures.
  • The resolved model (new spec/11 CP-6). It records regions, symbols and computations. Each computation has its reads (tracked SC-1, snapshot, pre C-2), its writes (W-1, AC-3..5), its calls and the computation it is nested in. The model is compile-time only (CP-6e).
  • Lowering (CP-1). emit.rs and awaits.rs port ref/compile/emit.ts and awaits.ts into a JS tree (js.rs).
    • oxc.rs builds the tree as an oxc_ast::Program for compile_program, for the universal, dom and ssr targets.
    • Scoped styles go through compile_style.
    • Solid is pinned to 2.0.0-rc.19 (d2323b0) in compiler/Cargo.toml, equal to ref/SOLID_NEXT in Repin to Solid 2.0.0-rc.19: zero recorded divergences #9.
  • Spans and source maps (spec/11 CP-1c, decisions 5 and 6). The program carries the lowered module's layout, and each layout position records its .rx line.
    • Solid's source maps are rewritten onto .rx.
    • Solid errors report file.rx:line.
  • Runners on the crate. With RX_CRATE=1, ref/compile/lowered.ts routes compile/run.ts and ssr/run.ts through the crate.
  • @reactivescript/compiler (spec/11 CP-7, decision 7). This replaces the earlier vite-plugin-rx/ prototype.
    • The build. The crate's Node-API build (--features node, src/node.rs, the same work as rxc app), with one binary per platform (npm/), as @solidjs/compiler ships.
    • The Vite front end is at /vite. An .rx module that JavaScript imports is a program's entry, so no option names it. Dependencies are imported as ?rx-dep, and a module shared by two programs is one module (CP-4b module, keyed by its factory).
    • CI and release. .github/workflows/compiler.yml builds and tests five platforms. On main it publishes a new version by trusted publishing, gated on the repo variable RELEASES. The one-time npm setup is in compiler/README.md under Releasing.

Verification (on rc.19, with #9 merged)

check result
npm --prefix ref run crate 280/280 programs agree: AST, diagnostics, .d.rx, model
npm --prefix ref run crate -- --lower 162/162 programs give identical JavaScript (210 modules × universal, dom, ssr); 630 source maps checked
RX_CRATE=1 npm --prefix ref run compiled 97 pass, 0 fail, 0 divergences
RX_CRATE=1 npm --prefix ref run ssr 90 pass, 0 fail, 7 skip (same as ref)
npm test in compiler/ (CI, 5 platforms) the binding compiles a program for every target. Windows caught a path bug, now fixed.
node compiler/test/vite.mjs 14 pass: every example app built by Vite 8 with plain solid() (#471) and run in Chromium, the todo app also under the dev server, and two programs sharing one module

Not yet done:

  • hot updates of .rx are untested;
  • server rendering through Vite needs the runtime package (CP-4a);
  • there is no WebAssembly fallback.

Decisions taken by default (veto in spec/11)

  1. The model is recorded during the checker's walk, not by a separate pass.
  2. The crate matches ref/ beyond verdicts, byte for byte.
  3. The program the crate hands to Solid carries the lowered module's layout, not .rx positions.
  4. .rx positions are lines, not columns.
  5. .rx in Vite is Solid's plugin loading @reactivescript/compiler, with Node-API builds per platform.

🤖 Generated with Claude Code

https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq

Starts the Rust port (spec/11 CP-1a) with its front half, per issue #1:
compiler/ is the `reactivescript` crate (binary `rxc`), a port of
ref/src/{parse,modules,content,signature,check}.ts, plus the resolved
model between check and lower (new CP-6): per module its regions,
symbols and computations, each with reads (tracked / snapshot / pre,
with probes and fields), writes and calls.

ref/ records the same model during the checker's walk (src/model.ts,
model.ts CLI). ref/crate.ts diffs the crate against ref/ on every
program in scenarios/ and examples/ (280 programs, 343 modules): AST,
diagnostics with messages, printed signatures and models all agree.

spec/11 gains CP-6 (the model) and CP-7 (the crate today), with two
decisions taken by default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
@ryansolid ryansolid self-assigned this Oct 27, 2026
compiler/ now lowers as ref/compile/emit.ts does (emit.rs, awaits.rs) into a
JS tree (js.rs), builds it as an oxc_ast::Program (oxc.rs) and hands it to
solidjs-compiler's compile_program (CP-1c), styles to compile_style (CP-1d).
Solid is pinned to ref/SOLID_NEXT.

ref/crate.ts --lower diffs both lowerings for universal, dom and ssr:
162/162 programs (210 modules) give the same JavaScript. RX_CRATE=1 runs
compile/run.ts on the crate's output: 97 pass, 0 fail, 9 divergences, as ref.

Solid's SSR props hoisting orders declarations and elements by span, so the
program carries the lowered module's layout rather than .rx positions
(spec/11 CP-1c note, decision 5, vetoable).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
@ryansolid ryansolid changed the title Rust crate: parse, check, signatures and the resolved model (CP-6) Rust crate: front half, resolved model (CP-6) and lowering through compile_program Oct 27, 2026
ref/compile/lowered.ts compiles a scenario program for the runners, by ref
(emit.ts + Solid's transform) or, with RX_CRATE=1, by `rxc lower`.
compile/run.ts and ssr/run.ts both go through it. On the crate's output:
compiled 97 pass, 0 fail, 9 divergences; ssr 90 pass, 0 fail, 7 skip —
the same as ref. The "use server" extraction stays Solid's
transformDirectives, a bundler pass applied to either output (spec/11 CP-7).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
Each lowered statement, JSX element and attribute records the .rx line it
was lowered from (S::At, a markup's line) at its layout position, so Solid's
source map is rewritten onto the .rx file and an error's (1:N) position is
reported as file.rx:line (spec/11 CP-1c, decision 6: lines, vetoable).

`rxc compile app.rx --out dir [--target]` writes modules, maps and the
stylesheet; `rxc lower` returns maps with `maps: true`. ref/crate.ts --lower
checks all 630 maps (declarations and elements land on their lines); an
off-by-one map fails 160 of 162 programs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
vite-plugin-rx/ compiles .rx through the crate (`rxc app`: the program
checked and compiled whole from its entry, imports by .rx path) and hands
Solid's Vite plugin plain JavaScript per environment (ssr or hydratable
dom), with the source map onto .rx and the stylesheet as a CSS module.
`node vite-plugin-rx/test.mjs` builds every example app with Vite 8 and
@solidjs/vite-plugin 3.0.0-next.50 and drives it in Chromium (todo also
under the dev server): 13 pass.

Solid's plugin has no front-end hook (.tsrx is routed by name; custom
extensions are parsed as JSX text), so spec/11 CP-7 notes one as an
upstream ask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
rx() is now a Frontend for @solidjs/vite-plugin's proposed `frontends`
option instead of a pre-plugin, so .rx takes the lowered .tsrx route
(lazy, refresh, server functions, CSS module). test.mjs runs against a
built plugin checkout (SOLID_VITE_PLUGIN): 13 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
JavaScript imports only a program's entry (a dependency is a factory over
the app, MO-1b), so a plainly imported .rx is an entry; the crate output's
.rx imports are tagged ?rx-dep and served from a program build containing
them. `rxc app` (by source) keys __rt.module by the factory, so a module
two programs share is one module. Test: two programs sharing
shop/catalog.rx (14 pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
…T in #9)

Checked against 2c9fdd9 with #9 merged locally: crate --lower 162/162
identical (630 maps), crate 280/280, RX_CRATE compiled 97/0 (0 divergences),
ssr 90/0/7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
…lain solid()

- The crate's Node-API build (`--features node`, src/node.rs) over `app`, now a
  library function (src/app.rs) that `rxc app` also calls; a missing entry is
  an error instead of an empty program.
- package.json for @reactivescript/compiler: one binary per platform
  (npm/*, as @solidjs/compiler ships), the napi loader, and the Vite front
  end at `/vite` (moved from vite-plugin-rx/, now calling the binding instead
  of spawning rxc). Solid's plugin loads it on its own
  (solidjs/solid-vite-plugin#471), so the examples build with `solid()`.
- .github/workflows/compiler.yml: build and test each platform; on main,
  publish a new version by trusted publishing and tag compiler-v<version>.
- spec/11: the Vite paragraph, and decision 7 (taken by default).

Tests: npm test (binding, every target); compiler/test/vite.mjs 14 pass on
plain solid(); crate conformance 280/280.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
The module graph keys modules by POSIX path (modules::join_path), so a
Windows entry (D:\a\main.rx) found none of its imports (MO-6). app() and
rxc's abs() now hand it D:/a/main.rx, which Windows opens alike. Found by
the compiler workflow's Windows test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
…OLID_NEXT in #9)

With #9 merged: crate 280/280, --lower 162/162 (630 maps), RX_CRATE=1
compiled 97/0 with no divergences, ssr 90/0/7; compiler/test/vite.mjs 14 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq
@ryansolid ryansolid changed the title Rust crate: front half, resolved model (CP-6) and lowering through compile_program Rust crate: front half, resolved model (CP-6), lowering through compile_program, and @reactivescript/compiler Oct 27, 2026
@ryansolid
ryansolid merged commit 08cb0dd into main Oct 27, 2026
7 checks passed
ryansolid pushed a commit that referenced this pull request Oct 27, 2026
compiler/src/check.rs: onSettled/onFlush checks (IO_IN_REACTIVE,
HOOK_CALLBACK, SETTLED_CLEANUP_UNOWNED) as in ref/src/check.ts.
compiler/src/emit.rs: lowers both to __rt.onSettled/__rt.onFlush.
npm --prefix ref run crate: 286/286 agree; --lower: 166/166.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWNyLJmSs7V2omSfCrh
ryansolid pushed a commit that referenced this pull request Oct 27, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjiThB5a8ekFuYHJXkD46h

# Conflicts:
#	ref/package.json
ryansolid pushed a commit that referenced this pull request Oct 27, 2026
…ding

The harness now takes rxc and rt-web.ts from this repo (main has the crate
since #6, pinned to rc.19). onFlush checked end to end: the tracker
reference clearing its error with onFlush in the action passes all hidden
tests; a plain write fails the one that asks for it at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117BoWKEv4MghW8Ns9LeUUX
ryansolid pushed a commit that referenced this pull request Oct 27, 2026
Conflicts: Cargo.toml's crate-type comment (both builds use the cdylib),
emit.rs's EF-7/EF-8 call and check.rs's returns_closure/hook check ported to
the span-carrying AST (ExprKind/StmtKind), spec/11's decisions renumbered
(main's 7 stays; the editor's are 8, 9, 10). The projection's BUILTINS and
prelude take onSettled/onFlush (ref/crate.ts --project 170/170).

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