Summary
When a module uses def initialize(...) + super(...) and is prepended on a Struct subclass, JRuby incorrectly warns "Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2" even though only positional arguments are being passed. For 1-member Structs, it crashes with a ClassCastException.
CRuby 3.4 produces no warnings and no crashes for identical code.
Environment
- JRuby 10.0.2.0 (3.4.2)
- OpenJDK 24.0.2+12-FR
Reproduction
#!/usr/bin/env ruby
# Reproduction: JRuby incorrectly handles `...` forwarding through `super` to Struct#initialize.
#
# Expected behavior (CRuby 3.4): no warnings, no crashes.
# Actual behavior (JRuby 10.0.2.0):
# - 1-member Struct: ClassCastException crash
# - 2-member Struct: works fine
# - 3+-member Struct: spurious "keyword arguments" warning
#
# Also affects `super(*args, **kwargs)` forwarding (3+ members warn).
$VERBOSE = true
module Wrapper
def initialize(...)
super(...)
end
end
# --- Case 1: 1-member Struct crashes ---
puts "--- Case 1: 1-member Struct ---"
begin
klass1 = Class.new(Struct.new(:a)) { prepend Wrapper }
klass1.new("x")
puts "OK"
rescue => e
puts "CRASH: #{e.class}: #{e.message}"
end
# --- Case 2: 2-member Struct works ---
puts "--- Case 2: 2-member Struct ---"
klass2 = Class.new(Struct.new(:a, :b)) { prepend Wrapper }
klass2.new("x", "y")
puts "OK"
# --- Case 3: 3-member Struct warns ---
puts "--- Case 3: 3-member Struct ---"
klass3 = Class.new(Struct.new(:a, :b, :c)) { prepend Wrapper }
klass3.new("x", "y", "z")
puts "OK"
# --- Case 4: bare `super` in def initialize(...) also triggers ---
puts "--- Case 4: bare super variant ---"
module WrapperBare
def initialize(...)
super
end
end
klass4 = Class.new(Struct.new(:a, :b, :c)) { prepend WrapperBare }
klass4.new("x", "y", "z")
puts "OK"
# --- Case 5: super(*args, **kwargs) also triggers ---
puts "--- Case 5: super(*args, **kwargs) ---"
module WrapperKwargs
def initialize(*args, **kwargs)
super(*args, **kwargs)
end
end
klass5 = Class.new(Struct.new(:a, :b, :c)) { include WrapperKwargs }
klass5.new("x", "y", "z")
puts "OK"
# --- Workaround: *args, &block without **kwargs does not warn ---
puts "--- Workaround: *args, &block ---"
module WrapperFixed
def initialize(*args, &block)
super(*args, &block)
end
end
klass6 = Class.new(Struct.new(:a, :b, :c)) { prepend WrapperFixed }
klass6.new("x", "y", "z")
puts "OK (no warning)"
Run this script on JRuby and MRI to observe the difference in behavior.
Behavior by member count
| Members |
Behavior |
Expected |
| 1 |
ClassCastException crash |
No crash, no warning |
| 2 |
Works fine |
Works fine |
| 3+ |
Spurious keyword argument warning |
No warning |
Also affects
def initialize(...) + super (bare super, no parens)
def initialize(*args, **kwargs) + super(*args, **kwargs) in included modules
Workaround
Using ruby2_keywords def initialize(*args, &block) + super(*args, &block) instead of def initialize(...) + super(...) avoids the bug.
Summary
When a module uses
def initialize(...)+super(...)and is prepended on a Struct subclass, JRuby incorrectly warns "Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2" even though only positional arguments are being passed. For 1-member Structs, it crashes with aClassCastException.CRuby 3.4 produces no warnings and no crashes for identical code.
Environment
Reproduction
Run this script on JRuby and MRI to observe the difference in behavior.
Behavior by member count
Also affects
def initialize(...) + super(bare super, no parens)def initialize(*args, **kwargs) + super(*args, **kwargs)in included modulesWorkaround
Using
ruby2_keywords def initialize(*args, &block) + super(*args, &block)instead ofdef initialize(...) + super(...)avoids the bug.