You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Eject: transpile once to a target language and leave the Ruby source behind, continuing development natively. Under eject the emitted tree is the product — readability, diff-ability, and use of the target's native affordances become functional requirements, not cosmetics. The argument is strongest where source and target idioms diverge most (Go, Rust, Kotlin, Swift), and the per-target known-unsupported ledger doubles as the new owner's hand-porting punch list.
Precedent: juntos (ruby2js) collapsed views back into EJS and JSX. The mechanism translates directly: roundhouse's pipeline already expands ERB to Ruby string-concat (_buf appends) and ruby2js's filter/erb.rb demonstrates collapsing exactly that compiled form back to template syntax (erb_to_jsx.rb is the direct route). Our lowering is collapse-friendly by construction.
Not a priority now. Demos would be more compelling with this work done, but it doesn't rise to priority yet. This issue is the catalog of what to explore either when eject truly becomes a priority, or as a polish pass after diminishing returns on new Rails features. The tier tags are the point: Tier B items cost almost nothing now if we record the intent fact whenever we next touch the relevant lowering — the polish stays deferred, but the fact isn't destroyed.
Tier A — pure emit-layer collapses
Pattern-match IR shapes we already have. Deferrable indefinitely, safe forever.
Record shapes. Row/Params classes → Kotlin data classes, C# records, Swift structs + Codable, Python dataclasses, Rust derives + serde, Crystal record, TS interfaces with object literals.
Nil idiom. Explicit .nil? check trees → Swift guard let/if let, Kotlin ?./?:, Rust Option combinators, C# nullable patterns, TS optional chaining.
Pattern matching. Case trees and class-id dispatch switches → Rust match, Kotlin when, Swift switch, C# switch expressions, Python match, Elixir multi-head functions.
Naming affordances. Predicate ? → is-prefix (isPersisted) on Kotlin/Swift/TS/C#/Go rather than the current Pred/_pred suffix; the affix sweep centralized the rename point, so this is a table change.
Tier B — needs an intent fact recorded in the IR
Additive metadata. Record it when next touching the relevant lowering; spend it later.
Generics re-generalization. Monomorphized specializations (ArticleCommentsProxy) carry their origin (CollectionProxy[Comment], fk article_id) so Rust/Kotlin/Swift/C#/Go/Crystal emitters can re-generalize (HasMany<Comment>) while other targets keep the specialization. The model-registry pattern (metadata in IR, emitters pick resolution) extended to generic origins — the one place idiom needs an IR schema addition rather than emit-layer work.
DSL/closure affordances. Routes, json_builder, broadcasts marked as builder-with-implicit-receiver → Kotlin lambdas-with-receiver, Swift @resultBuilder, C# LINQ/collection expressions, Elixir pipelines.
Resource lifecycle.Db.prepare/finalize marked as an acquire/release pair → Go defer, C# using, Python with, Rust Drop/RAII, Swift defer. Independent correctness value: eliminates the leaked-statement-on-early-return hazard class outright, which may justify pulling this one forward on its own.
Tier C — renovation-scale (only with eject as a driver)
Override seams → protocols/traits. Callbacks-as-overrides rendered as Rust traits with default methods, Swift protocol extensions, Go interfaces — instead of inheritance emulation and class-id switches.
Eject becomes a demo/positioning need — a juntos-style JSX view collapse is the single most demo-visible item here.
Diminishing returns on Rails-feature coverage (the polish-pass scenario).
Item 9's correctness angle justifying early adoption independent of idiom.
Design rule in the meantime
The catalog doubles as a checklist for new lowerings: before flattening a construct in shared lowering, check whether a Tier A/B item needs the fact being destroyed (self-describing IR). Keeping the _buf render-body shape stable and pattern-matchable is the standing example.
Use case
Eject: transpile once to a target language and leave the Ruby source behind, continuing development natively. Under eject the emitted tree is the product — readability, diff-ability, and use of the target's native affordances become functional requirements, not cosmetics. The argument is strongest where source and target idioms diverge most (Go, Rust, Kotlin, Swift), and the per-target known-unsupported ledger doubles as the new owner's hand-porting punch list.
Precedent: juntos (ruby2js) collapsed views back into EJS and JSX. The mechanism translates directly: roundhouse's pipeline already expands ERB to Ruby string-concat (
_bufappends) and ruby2js'sfilter/erb.rbdemonstrates collapsing exactly that compiled form back to template syntax (erb_to_jsx.rbis the direct route). Our lowering is collapse-friendly by construction.Not a priority now. Demos would be more compelling with this work done, but it doesn't rise to priority yet. This issue is the catalog of what to explore either when eject truly becomes a priority, or as a polish pass after diminishing returns on new Rails features. The tier tags are the point: Tier B items cost almost nothing now if we record the intent fact whenever we next touch the relevant lowering — the polish stays deferred, but the fact isn't destroyed.
Tier A — pure emit-layer collapses
Pattern-match IR shapes we already have. Deferrable indefinitely, safe forever.
_buf-append render bodies to: JSX/TSX (TypeScript), HEEx (Elixir — affinity with the live-updates arc, analysis: write/invalidation graph as a lowering (+ per-target revalidation policies; prereq: async-coloring into client contexts) #44), Razor (C#), templ or html/template (Go), kotlinx.html builders (Kotlin), a result-builder DSL (Swift). Ruby eject: re-emit the original ERB verbatim.AccessorKindalready survives into the IR: Kotlinvar title: Stringwith customget(), Swift computed properties, C# auto-properties, Python@property, Crystalproperty, Rubyattr_*(gated on spinel attr_reader flattening shadows an explicit subclass override at call sites (field access emitted instead of dispatch) matz/spinel#1702 — attr flattening currently shadows explicit overrides).record, TS interfaces with object literals..nil?check trees → Swiftguard let/if let, Kotlin?./?:, RustOptioncombinators, C# nullable patterns, TS optional chaining.match, Kotlinwhen, Swiftswitch, C# switch expressions, Pythonmatch, Elixir multi-head functions.?→is-prefix (isPersisted) on Kotlin/Swift/TS/C#/Go rather than the currentPred/_predsuffix; the affix sweep centralized the rename point, so this is a table change.Tier B — needs an intent fact recorded in the IR
Additive metadata. Record it when next touching the relevant lowering; spend it later.
ArticleCommentsProxy) carry their origin (CollectionProxy[Comment], fkarticle_id) so Rust/Kotlin/Swift/C#/Go/Crystal emitters can re-generalize (HasMany<Comment>) while other targets keep the specialization. The model-registry pattern (metadata in IR, emitters pick resolution) extended to generic origins — the one place idiom needs an IR schema addition rather than emit-layer work.json_builder, broadcasts marked as builder-with-implicit-receiver → Kotlin lambdas-with-receiver, Swift@resultBuilder, C# LINQ/collection expressions, Elixir pipelines.Db.prepare/finalizemarked as an acquire/release pair → Godefer, C#using, Pythonwith, Rust Drop/RAII, Swiftdefer. Independent correctness value: eliminates the leaked-statement-on-early-return hazard class outright, which may justify pulling this one forward on its own.Tier C — renovation-scale (only with eject as a driver)
suspend, Swift/C#/Rust native async, Go goroutines, Elixir processes. Already has its own thread via async-coloring and live-updates (analysis: write/invalidation graph as a lowering (+ per-target revalidation policies; prereq: async-coloring into client contexts) #44); lands there, not as polish.Triggers
Design rule in the meantime
The catalog doubles as a checklist for new lowerings: before flattening a construct in shared lowering, check whether a Tier A/B item needs the fact being destroyed (self-describing IR). Keeping the
_bufrender-body shape stable and pattern-matchable is the standing example.