Skip to content

Spurious Struct#initialize keyword argument warning (and crash) with ... forwarding through super #9242

@myronmarston

Description

@myronmarston

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions