Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression in constructor lookup for subclasses of reopened Java classes #6966

Open
ikaronen-relex opened this issue Dec 9, 2021 · 2 comments

Comments

@ikaronen-relex
Copy link
Contributor

ikaronen-relex commented Dec 9, 2021

It seems that there have been some changes in JRuby 9.3 to the way Java and Ruby constructors interact when a reopened Java class with an initialize method is subclassed. I would consider these changes to be regressions; at least, if they are in fact intentional, I have seen no documentation explaining them.

In particular, the following specs (with accompanying Java classes below) all pass on JRuby 9.2.19.0, but four of them fail on JRuby 9.3.2.0:

describe "JRuby Java inheritance with JRuby #{JRUBY_VERSION}" do
  class Java::Test::JRubyInheritanceTest
    def initialize(*args)
      opts = args.empty? ? nil : args[-1]
      super(opts ? opts[:arg] : "no options")
      trace.add "Ruby parent constructor called with #{args.inspect}"
    end
  end

  class Java::Test::JRubyInheritanceTestSubclass
    # no constructor
  end

  subclass_with_ctor = Class.new(Java::Test::JRubyInheritanceTest) do
    def initialize(*args)
      super(*args)
      trace.add "Ruby subclass constructor called with #{args.inspect}"
    end
  end

  subsubclass_with_ctor = Class.new(Java::Test::JRubyInheritanceTestSubclass) do
    def initialize(*args)
      super(*args)
      trace.add "Ruby subsubclass constructor called with #{args.inspect}"
    end
  end

  shared_examples 'stuff' do |args|
    java_arg = args.empty? ? "no options" : args[-1][:arg]

    it 'calls Ruby constructors first when constructing parent class' do
      obj = Java::Test::JRubyInheritanceTest.new(*args)
      expect(obj.trace).to eq(["Java parent constructor called with #{java_arg}",
                               "Ruby parent constructor called with #{args.inspect}"])
    end

    it 'calls Ruby constructors first when constructing Ruby subclass' do
      subclass = Class.new(Java::Test::JRubyInheritanceTest)
      obj = subclass.new(*args)
      expect(obj.trace).to eq(["Java parent constructor called with #{java_arg}",
                               "Ruby parent constructor called with #{args.inspect}"])
    end

    it 'calls Ruby constructors first when constructing Ruby subclass with constructor' do
      obj = subclass_with_ctor.new(*args)
      expect(obj.trace).to eq(["Java parent constructor called with #{java_arg}",
                               "Ruby parent constructor called with #{args.inspect}",
                               "Ruby subclass constructor called with #{args.inspect}"])
    end

    it 'calls Ruby constructors first when constructing Java subclass' do
      obj = Java::Test::JRubyInheritanceTestSubclass.new(*args)
      expect(obj.trace).to eq(["Java parent constructor called with #{java_arg}",
                               "Java subclass constructor called with #{java_arg}",
                               "Ruby parent constructor called with #{args.inspect}"])
    end

    it 'calls Ruby constructors first when constructing Ruby subclass of Java subclass' do
      subclass = Class.new(Java::Test::JRubyInheritanceTestSubclass)
      obj = subclass.new(*args)
      expect(obj.trace).to eq(["Java parent constructor called with #{java_arg}",
                               "Java subclass constructor called with #{java_arg}",
                               "Ruby parent constructor called with #{args.inspect}"])
    end

    it 'calls Ruby constructors first when constructing Ruby subclass of Java subclass with constructor' do
      obj = subsubclass_with_ctor.new(*args)
      expect(obj.trace).to eq(["Java parent constructor called with #{java_arg}",
                               "Java subclass constructor called with #{java_arg}",
                               "Ruby parent constructor called with #{args.inspect}",
                               "Ruby subsubclass constructor called with #{args.inspect}"])
    end
  end

  context('with options hash') { include_examples 'stuff', [{arg: 'foo'}] }
  context('with no args') { include_examples 'stuff', [] }
end

Accompanying Java classes:

package test;

import java.util.ArrayList;

public class JRubyInheritanceTest {
    private final ArrayList<String> trace = new ArrayList<String>();

    public ArrayList<String> getTrace() {
        return trace;
    }

    public JRubyInheritanceTest(String s) {
        getTrace().add("Java parent constructor called with " + s);
    }

    public JRubyInheritanceTest() {
        this("no args");
    }
}
package test;

public class JRubyInheritanceTestSubclass extends JRubyInheritanceTest {
    public JRubyInheritanceTestSubclass (String s) {
        super(s);
        getTrace().add("Java subclass constructor called with " + s);
    }
}

RSpec output on JRuby 9.2.19.0:

JRuby Java inheritance with JRuby 9.2.19.0
  with options hash
    calls Ruby constructors first when constructing parent class
    calls Ruby constructors first when constructing Ruby subclass
    calls Ruby constructors first when constructing Ruby subclass with constructor
    calls Ruby constructors first when constructing Java subclass
    calls Ruby constructors first when constructing Ruby subclass of Java subclass
    calls Ruby constructors first when constructing Ruby subclass of Java subclass with constructor
  with no args
    calls Ruby constructors first when constructing parent class
    calls Ruby constructors first when constructing Ruby subclass
    calls Ruby constructors first when constructing Ruby subclass with constructor
    calls Ruby constructors first when constructing Java subclass
    calls Ruby constructors first when constructing Ruby subclass of Java subclass
    calls Ruby constructors first when constructing Ruby subclass of Java subclass with constructor

Finished in 0.06475 seconds (files took 0.36651 seconds to load)
12 examples, 0 failures

RSpec output on JRuby 9.3.2.0:

JRuby Java inheritance with JRuby 9.3.2.0
  with options hash
    calls Ruby constructors first when constructing parent class
    calls Ruby constructors first when constructing Ruby subclass
    calls Ruby constructors first when constructing Ruby subclass with constructor (FAILED - 1)
    calls Ruby constructors first when constructing Java subclass
    calls Ruby constructors first when constructing Ruby subclass of Java subclass
    calls Ruby constructors first when constructing Ruby subclass of Java subclass with constructor (FAILED - 2)
  with no args
    calls Ruby constructors first when constructing parent class
    calls Ruby constructors first when constructing Ruby subclass
    calls Ruby constructors first when constructing Ruby subclass with constructor (FAILED - 3)
    calls Ruby constructors first when constructing Java subclass
    calls Ruby constructors first when constructing Ruby subclass of Java subclass
    calls Ruby constructors first when constructing Ruby subclass of Java subclass with constructor (FAILED - 4)

Failures:

  1) JRuby Java inheritance with JRuby 9.3.2.0 with options hash calls Ruby constructors first when constructing Ruby subclass with constructor
     Failure/Error: obj = subclass_with_ctor.new(*args)
     
     ArgumentError:
       wrong number of arguments for constructor
     Shared Example Group: "stuff" called from ./spec/internals/jruby_inheritance_spec.rb:75
     # ./spec/internals/jruby_inheritance_spec.rb:45:in `block in <main>'

  2) JRuby Java inheritance with JRuby 9.3.2.0 with options hash calls Ruby constructors first when constructing Ruby subclass of Java subclass with constructor
     Failure/Error: obj = subsubclass_with_ctor.new(*args)
     
     ArgumentError:
       wrong number of arguments for constructor
     Shared Example Group: "stuff" called from ./spec/internals/jruby_inheritance_spec.rb:75
     # ./spec/internals/jruby_inheritance_spec.rb:67:in `block in <main>'

  3) JRuby Java inheritance with JRuby 9.3.2.0 with no args calls Ruby constructors first when constructing Ruby subclass with constructor
     Failure/Error:
       expect(obj.trace).to eq(["Java parent constructor called with #{java_arg}",
                                "Ruby parent constructor called with #{args.inspect}",
                                "Ruby subclass constructor called with #{args.inspect}"])
     
       expected: ["Java parent constructor called with no options", "Ruby parent constructor called with []", "Ruby subclass constructor called with []"]
            got: #<Java::JavaUtil::ArrayList: ["Java parent constructor called with no args", "Ruby subclass constructor called with []"]>
     
       (compared using ==)
     Shared Example Group: "stuff" called from ./spec/internals/jruby_inheritance_spec.rb:76
     # ./spec/internals/jruby_inheritance_spec.rb:46:in `block in <main>'

  4) JRuby Java inheritance with JRuby 9.3.2.0 with no args calls Ruby constructors first when constructing Ruby subclass of Java subclass with constructor
     Failure/Error: obj = subsubclass_with_ctor.new(*args)
     
     ArgumentError:
       wrong number of arguments for constructor
     Shared Example Group: "stuff" called from ./spec/internals/jruby_inheritance_spec.rb:76
     # ./spec/internals/jruby_inheritance_spec.rb:67:in `block in <main>'

Finished in 0.11702 seconds (files took 0.39369 seconds to load)
12 examples, 4 failures

Failed examples:

rspec ./spec/internals/jruby_inheritance_spec.rb[1:1:3] # JRuby Java inheritance with JRuby 9.3.2.0 with options hash calls Ruby constructors first when constructing Ruby subclass with constructor
rspec ./spec/internals/jruby_inheritance_spec.rb[1:1:6] # JRuby Java inheritance with JRuby 9.3.2.0 with options hash calls Ruby constructors first when constructing Ruby subclass of Java subclass with constructor
rspec ./spec/internals/jruby_inheritance_spec.rb[1:2:3] # JRuby Java inheritance with JRuby 9.3.2.0 with no args calls Ruby constructors first when constructing Ruby subclass with constructor
rspec ./spec/internals/jruby_inheritance_spec.rb[1:2:6] # JRuby Java inheritance with JRuby 9.3.2.0 with no args calls Ruby constructors first when constructing Ruby subclass of Java subclass with constructor

Note that in three of the four failing cases the spec crashes with "ArgumentError: wrong number of arguments for constructor", while in one case (no args, Ruby subclass of Java parent class, with subclass constructor) the Ruby subclass constructor ends up calling the no-arg Java parent class constructor and skipping the Ruby parent class constructor entirely.

@ikaronen-relex
Copy link
Contributor Author

ikaronen-relex commented Dec 9, 2021

Ps. This might be related to the changes in #6820, but that's just a hunch.

EDIT (by @kares): believe this is caused by the work introduced at #6422

@ccutrer
Copy link
Contributor

ccutrer commented Jul 3, 2022

probably related:

given this code:

class A < java.lang.Number
    def initialize(config: )
        super()
        puts config.inspect
    end
    
    def m
        puts "a"
    end
end

module B
    def m
        puts "b"
        super
    end
end

A.new(config: {}).m

puts

A.prepend(B)

A.new(config: {}).m

puts

A.new.m

I would expect the output to be

{}
a

{}
b
a

ArgumentError (wrong number of arguments for constructor)

but instead, I get

{}
a

ArgumentError (wrong number of arguments for constructor)

b
a

ruby version: jruby 9.3.6.0 (2.6.8) 2022-06-27 7a2cbcd376 OpenJDK 64-Bit Server VM 11.0.15+10-Ubuntu-0ubuntu0.22.04.1 on 11.0.15+10-Ubuntu-0ubuntu0.22.04.1 [x86_64-linux]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants