Probed at main e0d8610, Linux x86-64, gcc 16.2.1 (Spinel f672bd97a for the binary).
Two jbuilder partial shapes render {}, with no diagnostic: a partial whose body is wrapped in begin … rescue … end, and json.partial! @record (the partial resolved from the record, as Rails does). Both show on --target ruby (CRuby, through the emitted main.rb) and on the --target spinel binary.
app/views/widgets/:
# _widget.json.jbuilder
json.id widget.id
json.name widget.name
# _guarded_widget.json.jbuilder
begin
json.id widget.id
json.name widget.name
rescue StandardError
json.error "unavailable"
end
# show.json.jbuilder
json.partial! "widgets/widget", widget: @widget
# detail.json.jbuilder
json.partial! @widget
# guarded.json.jbuilder
json.partial! "widgets/guarded_widget", widget: @widget
app/controllers/widgets_controller.rb:
class WidgetsController < ApplicationController
def show
@widget = Widget.find(params[:id])
render :show
end
def detail
@widget = Widget.find(params[:id])
render :detail
end
def guarded
@widget = Widget.find(params[:id])
render :guarded
end
end
Rest of the fixture
config/routes.rb
Rails.application.routes.draw do
get "widgets/:id", to: "widgets#show", defaults: { format: :json }
get "widgets/:id/detail", to: "widgets#detail", defaults: { format: :json }
get "widgets/:id/guarded", to: "widgets#guarded", defaults: { format: :json }
end
db/schema.rb
ActiveRecord::Schema[8.1].define(version: 2026_01_01_000000) do
create_table "widgets", force: :cascade do |t|
t.string "name"
t.integer "size"
end
end
app/models/widget.rb: class Widget < ApplicationRecord; end. Gemfile: rails, jbuilder. ApplicationRecord / ApplicationController (< ActionController::Base) as generated.
Seeded with one row, name: "b":
| request |
Rails 8.1.4 + jbuilder 2.15.1 |
--target ruby (CRuby 4.0.5) and the spinel binary |
GET /widgets/1 |
{"id":1,"name":"b"} |
{"id":1,"name":"b"} |
GET /widgets/1/detail |
{"id":1,"name":"b"} |
{} |
GET /widgets/1/guarded |
{"id":1,"name":"b"} |
{} |
(The Rails column is the fixture booted on real Rails with sqlite in memory and the three requests sent through Rack.)
Emitted (--target ruby; the spinel tree has the same .rb):
# app/views/widgets/_guarded_widget_json.rb
def self.guarded_widget_json(widget)
io = String.new
io << "{"
io << ""
io << "}"
io
end
# app/views/widgets/detail_json.rb
def self.detail_json(widget)
io = String.new
io << "{"
io << ""
io << "}"
io
end
What they should be: guarded_widget_json the same body as widget_json (the "id" and "name" pairs) inside a begin … rescue StandardError … end whose rescue branch emits the "error" pair instead; and detail_json the call show_json already makes, io << Views::Widgets.widget_json(widget).
Where it goes
Both fall to JbStmt::Unknown in src/lower/jbuilder_to_library/mod.rs, which emit_object writes as an empty append inside { … }:
stmts_of only opens a Seq, so a template whose one statement is an ExprNode::BeginRescue is a single statement classify does not recognise.
- The
"partial!" arm needs a string-literal path (string_literal(path_arg)) and an options Hash. Jbuilder's partial! with one ActiveModel argument renders <model.to_partial_path> with the record bound to the partial's local (widgets/_widget, local widget), which for an ivar/local of a known model class is the path "<plural>/<singular>" and the arg itself.
Neither is reported (no ingest gap; with --allow-unsupported the only warnings on these templates are unresolved_type for the widget reads), so the template silently answers {}. A diagnostic for Unknown statements would have pointed at both.
Found while compiling a Rails API app with --target spinel.
Probed at main e0d8610, Linux x86-64, gcc 16.2.1 (Spinel f672bd97a for the binary).
Two jbuilder partial shapes render
{}, with no diagnostic: a partial whose body is wrapped inbegin … rescue … end, andjson.partial! @record(the partial resolved from the record, as Rails does). Both show on--target ruby(CRuby, through the emittedmain.rb) and on the--target spinelbinary.app/views/widgets/:app/controllers/widgets_controller.rb:Rest of the fixture
config/routes.rbdb/schema.rbapp/models/widget.rb:class Widget < ApplicationRecord; end.Gemfile:rails,jbuilder.ApplicationRecord/ApplicationController(< ActionController::Base) as generated.Seeded with one row,
name: "b":--target ruby(CRuby 4.0.5) and the spinel binaryGET /widgets/1{"id":1,"name":"b"}{"id":1,"name":"b"}GET /widgets/1/detail{"id":1,"name":"b"}{}GET /widgets/1/guarded{"id":1,"name":"b"}{}(The Rails column is the fixture booted on real Rails with sqlite in memory and the three requests sent through Rack.)
Emitted (
--target ruby; the spinel tree has the same.rb):What they should be:
guarded_widget_jsonthe same body aswidget_json(the"id"and"name"pairs) inside abegin … rescue StandardError … endwhose rescue branch emits the"error"pair instead; anddetail_jsonthe callshow_jsonalready makes,io << Views::Widgets.widget_json(widget).Where it goes
Both fall to
JbStmt::Unknowninsrc/lower/jbuilder_to_library/mod.rs, whichemit_objectwrites as an empty append inside{ … }:stmts_ofonly opens aSeq, so a template whose one statement is anExprNode::BeginRescueis a single statementclassifydoes not recognise."partial!"arm needs a string-literal path (string_literal(path_arg)) and an options Hash. Jbuilder'spartial!with one ActiveModel argument renders<model.to_partial_path>with the record bound to the partial's local (widgets/_widget, localwidget), which for an ivar/local of a known model class is the path"<plural>/<singular>"and the arg itself.Neither is reported (no ingest gap; with
--allow-unsupportedthe only warnings on these templates areunresolved_typefor thewidgetreads), so the template silently answers{}. A diagnostic forUnknownstatements would have pointed at both.Found while compiling a Rails API app with
--target spinel.