Skip to content

Spurious Struct#initialize keyword argument warning re-occurring #9328

@rossroberts-toast

Description

@rossroberts-toast

Environment Information

  • JRuby 10.0.4.0 (Ruby 3.4.5 compat)
  • macOS arm64-darwin

Expected Behavior

Issue #9242 was reported as fixed, but the fix seems to be incomplete. Using def initialize(...) + super(...) in a module prepended/included on a Struct subclass should not produce any warnings. CRuby 3.4.4 produces no warnings:

Given the script: jruby_9242_struct_initialize.rb:

#!/usr/bin/env ruby
# frozen_string_literal: true

# JRuby #9242: def initialize(...) + super(...) in a module prepended on a
# Struct subclass incorrectly warns about keyword arguments (3+ members)
# or crashes with ClassCastException (1 member).
#
# Run: ruby jruby_9242_struct_initialize.rb 2>&1

require "stringio"

puts "Ruby: #{RUBY_DESCRIPTION}"
puts

old_stderr = $stderr
$stderr = StringIO.new

mod = Module.new do
  def initialize(...)
    super(...)
  end
end

# Test 1: 3-member Struct (triggers spurious keyword warning)
puts "1. Struct with 3 members + prepended initialize(...):"
S3 = Struct.new(:a, :b, :c) { prepend mod }
obj3 = S3.new(1, 2, 3)
puts "   a=#{obj3.a} b=#{obj3.b} c=#{obj3.c}"
stderr_output = $stderr.string
$stderr = StringIO.new
if stderr_output.include?("keyword")
  puts "   FAIL: spurious warning: #{stderr_output.strip}"
else
  puts "   PASS: no spurious warning"
end
puts

# Test 2: 1-member Struct (used to crash with ClassCastException)
puts "2. Struct with 1 member + prepended initialize(...):"
begin
  S1 = Struct.new(:x) { prepend mod }
  obj1 = S1.new(1)
  stderr_output = $stderr.string
  $stderr = StringIO.new
  if stderr_output.include?("keyword")
    puts "   x=#{obj1.x}"
    puts "   FAIL: spurious warning: #{stderr_output.strip}"
  else
    puts "   x=#{obj1.x}"
    puts "   PASS: no crash, no warning"
  end
rescue => e
  puts "   FAIL: #{e.class}: #{e.message}"
end
puts

# Test 3: Same but with include instead of prepend
puts "3. Struct with 3 members + included initialize(...):"
$stderr = StringIO.new
S3i = Struct.new(:a, :b, :c) { include mod }
obj3i = S3i.new(1, 2, 3)
puts "   a=#{obj3i.a} b=#{obj3i.b} c=#{obj3i.c}"
stderr_output = $stderr.string
if stderr_output.include?("keyword")
  puts "   FAIL: spurious warning: #{stderr_output.strip}"
else
  puts "   PASS: no spurious warning"
end

$stderr = old_stderr

On Ruby: ruby 3.4.4

  1. Struct with 3 members + prepended initialize(...):
    a=1 b=2 c=3
    PASS: no spurious warning

  2. Struct with 1 member + prepended initialize(...):
    x=1
    PASS: no crash, no warning

  3. Struct with 3 members + included initialize(...):
    a=1 b=2 c=3
    PASS: no spurious warning

On JRuby 10.0.4.0

The 1-member Struct crash (ClassCastException) is fixed, but Struct subclasses with 3+ members still produce a spurious warning:

  1. Struct with 3 members + prepended initialize(...):
    a=1 b=2 c=3
    FAIL: spurious warning: Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2. Please use a Hash literal like .new({k: v}) instead of .new(k: v).

  2. Struct with 1 member + prepended initialize(...):
    x=1
    PASS: no crash, no warning

  3. Struct with 3 members + included initialize(...):
    a=1 b=2 c=3
    FAIL: spurious warning: ...

The warning is incorrect — S3.new(1, 2, 3) passes positional arguments, not keyword arguments. The warning is triggered because the module's initialize(...) + super(...) forwarding pattern is misinterpreted by JRuby as keyword argument passing.

Our workaround is to use ruby2_keywords to avoid separate **kwargs forwarding in the affected modules.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions