Skip to content

Commit 2836e11

Browse files
Omar Qureshiclaude
andcommitted
fix(ruby): emit statics only on their defining class
Static stubs were re-emitted on every subclass (allMethods/allProperties include inherited members) with the *derived* class fqn interpolated into call_static/get_static — baking the wrong type into the kernel call for inherited statics. Ruby singleton methods inherit, which matches the ES6 static-inheritance semantics the kernel implements (its lookups walk the base chain), so the fix is to not re-emit at all: a static is generated only where definingType matches the emitted class. Children that override a static still get their own stub (allMethods yields the most-derived declaration), as pinned by the StaticHelloParent/Child fixture. Generated-code effect on jsii-calc: JSII417Derived no longer re-emits make_instance (now resolved via inheritance from JSII417PublicBaseOfBase, through two erased private bases); StaticHelloChild keeps its overriding stubs with the child fqn. Tests: both paths covered in classes_spec (pure inheritance via JSII417Derived, override via StaticHelloChild). Full suite: 235 examples, 0 failures; compliance report unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 720b6b3 commit 2836e11

2 files changed

Lines changed: 38 additions & 1 deletion

File tree

packages/@jsii/ruby-runtime-test/spec/compliance/classes_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,31 @@ def unwrap(gen)
219219
end
220220
end
221221

222+
describe 'static inheritance (extended)' do
223+
# jsii's StaticHello fixture documents the design fork: ES6 inherits
224+
# statics through the class hierarchy, Java does not. Ruby naturally
225+
# matches ES6 — singleton methods inherit — so the generator emits a
226+
# static only on its *defining* class and lets Ruby inheritance (and the
227+
# kernel's base-chain lookup) do the rest.
228+
it 'resolves inherited statics through singleton-method inheritance' do
229+
# makeInstance is defined on JSII417PublicBaseOfBase (two erased
230+
# private classes up); JSII417Derived re-emits nothing — the call
231+
# resolves to the base stub, carrying the base fqn to the kernel.
232+
expect(JsiiCalc::JSII417Derived.make_instance).to be_a(JsiiCalc::JSII417PublicBaseOfBase)
233+
end
234+
235+
it 'lets a child override parent statics (ES6 semantics)' do
236+
expect(JsiiCalc::StaticHelloParent.PROPERTY).to eq(1337)
237+
expect(JsiiCalc::StaticHelloParent.property).to eq(1337)
238+
239+
# The child's own stubs shadow the inherited ones and carry the
240+
# child's fqn.
241+
expect(JsiiCalc::StaticHelloChild.PROPERTY).to eq(42)
242+
expect(JsiiCalc::StaticHelloChild.property).to eq(42)
243+
expect { JsiiCalc::StaticHelloChild.method() }.not_to raise_error
244+
end
245+
end
246+
222247
describe 'inheritance (extended)' do
223248
it 'handles inheritance with no new properties' do
224249
# DerivedClassHasNoProperties::Derived inherits from Base

packages/jsii-pacmak/lib/targets/ruby.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,16 @@ export class RubyGenerator extends Generator {
636636
}
637637
this.code.line('');
638638

639+
// Static members are emitted only on their *defining* class. Ruby
640+
// inherits singleton methods, which matches the ES6 static-inheritance
641+
// semantics the kernel implements (its method/property lookups walk the
642+
// base chain, and the base's stub carries the base fqn). Re-emitting an
643+
// inherited static here would bake the *derived* fqn into the kernel
644+
// call instead. A child that overrides a static still gets its own
645+
// stub, because allMethods/allProperties yield the most-derived
646+
// declaration (see the StaticHelloParent/Child fixture in jsii-calc).
647+
const isOwnStatic = (m: any) => m.definingType?.fqn === typeSpec.fqn;
648+
639649
const overridableMethods = resolvedAllMethods.filter((m: any) => !m.static);
640650
const overridableProps = resolvedAllProperties.filter(
641651
(p: any) => !p.static,
@@ -663,7 +673,7 @@ export class RubyGenerator extends Generator {
663673
this.code.line('');
664674

665675
for (const method of resolvedAllMethods) {
666-
if (!method.static) continue;
676+
if (!method.static || !isOwnStatic(method)) continue;
667677

668678
const sigParams = method.parameters
669679
.map((p: any) => {
@@ -701,6 +711,8 @@ export class RubyGenerator extends Generator {
701711
}
702712

703713
for (const prop of resolvedAllProperties) {
714+
if (prop.static && !isOwnStatic(prop)) continue;
715+
704716
const rubyName = this.rubyPropertyName(prop);
705717

706718
if (prop.static) {

0 commit comments

Comments
 (0)