Skip to content

lowerer: compile-time class-body declarations (enum / attribute / literal define_method) for Writebook + Lobsters #30

Description

@rubys

Motivation

Inspired by matz/spinel#1328 ("Add compile-time class-body declarations"), which adds a compile-time-resolvable path for class-body metaprogramming (attribute/attributes, literal-name define_method, literal .each method generation) while rejecting dynamic metaprogramming during analysis. That is exactly roundhouse's "transpile-time-resolvable Ruby" thesis.

This issue inventories the class-body declaration / metaprogramming gaps surfaced by auditing two anchor apps. Scope: this is the metaprogramming/DSL-expansion dimension only — it is not a complete port checklist for either app.

Today roundhouse recognizes only a fixed DSL set in src/ingest/model.rs (validates, scope, callbacks, has_many/has_one/belongs_to, plus marker/broadcasts_to special-cases in src/lower/model_to_library/). Everything else becomes ModelBodyItem::Unknown — re-emitted verbatim by the Ruby emitter, but producing nothing usable for the nominal targets (Rust/Go/Crystal/etc.).

What the anchor apps actually use

Writebook (~/git/writebook)

  • enum ×5 — book.rb (enum :theme, %w[black blue green magenta orange violet white].index_by(&:itself), suffix: true, default: :blue), edit.rb, access.rb, leaf.rb, user/role.rb (enum :role, %i[member administrator], default: :member). Canonical literal-array → method generation (predicates, bang methods, scopes, mappings). Currently dropped to Unknown.
  • attribute — current.rb:2 attribute :session, :user (ActiveSupport::CurrentAttributes form).
  • literal define_method ×3 — app/models/concerns/positionable.rb:19-29, defined inside the parametric class-method macro positioned_within(parent, association:, filter:). Single call site, fully literal args: leaf.rb:6 positioned_within :book, association: :leaves, filter: :active. The generated methods are required by Leaf (previous/next/move_to_position depend on them), so emitting Unsupported here breaks Leaf.
  • class_eval <<-CODE heredoc — lib/rails_ext/action_text_has_markdown.rb:7. Genuinely dynamic string-eval → should be Unsupported, never expanded.

Lobsters (~/git/ruby-bench/benchmarks/lobsters)

  • attribute typed/virtual columns — replying_comment.rb:2 attribute :is_unread, :boolean, message.rb:14 attribute :mod_note, :boolean.
  • No class-body define_method in app/lib. ([:error,:success,:notice].each do |f| exists only in a view — runtime loop, already fine.)

Work items

  1. enum lowerer (highest leverage). Recognize enum :name, <literal> (%w[...].index_by(&:itself), %i[...], hash forms) + suffix:/prefix:/default: options. Expand into generated predicate/bang/scope methods + value↔int mapping, lowered to IR so all targets get real code instead of passthrough-Unknown. Covers 5 Writebook sites.

  2. attribute / attributes lowerer. Typed virtual attributes (attribute :x, :boolean) → typed accessor + default; recognize the CurrentAttributes (Current) form. Covers both apps.

  3. Compile-time define_method + concern-macro inlining (medium leverage, harder). Partial-evaluate an ActiveSupport::Concern class_methods macro at its literal call site, substituting literal args, then expand literal-name define_method bodies into real method defs (incl. collapsing send(:literal) → direct call). Required for Writebook Leaf. Single literal call site = best case for the bounded expander.

  4. Reject dynamic metaprogramming via diagnostics (diagnostics: structured "unsupported construct" reporting from lowerers + emitters (degrade, don't panic) #28 machinery). class_eval/module_eval/string-eval/non-literal define_method → structured Unsupported diagnostic, never expanded.

Suggested order

1 (enum) → 2 (attribute) → 4 (reject dynamic) → 3 (define_method/concern inlining). Items 1–2 are targeted DSL lowerers that slot next to associations.rs/validations.rs; item 4 reuses existing diagnostics; item 3 is the only net-new machinery.

Refs: #21 (lobsters anchor), #28 (unsupported diagnostics).

Activity

  1. thomasklemm commented on Oct 10, 2026

    @thomasklemm
    Collaborator

    Compatibility follow-up tracked in thomasklemm/roundhouse#69: the latest Writebook source tested was 6ee6afd2b79b8ea31527510aa8d5d6142b19e217. Its source Page/MarkdownRenderer/HtmlScrubber tests pass (20 tests, 150 assertions), but the emitted Ruby Page test currently stops at the unsupported Rouge::Plugins::Redcarpet include during boot. This is adjacent to #30 but distinct from enum/attribute/define_method expansion: we need a reusable external-gem load/runtime boundary, and should keep diagnostics until emitted rendering behavior is proven.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions