-
-
Notifications
You must be signed in to change notification settings - Fork 102
Workaround JRuby incompatibilities #345
Workaround JRuby incompatibilities #345
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @grddev! I left a few comments.
else | ||
@min_non_kw_args = arity | ||
@max_non_kw_args = arity | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 6 lines (from if arity < 0
to the end
) show up 3 times in this file. Can you extract a helper method (something like classify_arity(arity)
) for this code so it's duplicated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
|
||
if RSpec::Support::Ruby.jruby? && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of starting a 2nd conditional that starts with if RSpec::Support::Ruby.jruby?
, would you mind doing one big if RSpec::Support::Ruby.jruby?
conditional (with 2 nested conditionals as needed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, is it possible to combine the two Jruby cases into one class, with some extra conditional checked when classify_parameters
is called? It's confusing (and will be harder to maintain) to have two different alternate JRuby implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The motivation for having them separate is that the latter one cannot use #parameters
, but it could obviously be implemented by checking support for #parameters
at runtime, if you prefer such a solution.
if RSpec::Support::Ruby.jruby? && | ||
RubyFeatures.optional_and_splat_args_supported? && | ||
Class.new { attr_writer :foo }.instance_method(:foo=).parameters == [] | ||
Java::JavaLang::String.instance_method(:char_at).parameters == [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to understand why you changed this conditional, and how this changes things. Does Class.new { attr_writer :foo }.instance_method(:foo=).parameters == []
return the same value on all versions of JRuby that Java::JavaLang::String.instance_method(:char_at).parameters == []
does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As should hopefully be clear by the table in my overall comment, there are no versions where the attr_writer
bug exists, but the Java proxy method one does not.
end | ||
|
||
def java_method_arity | ||
if @method.owner.respond_to?(:java_class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to use an early-exit guard, like so:
return unless @method.owner.respond_to?(:java_class)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
end | ||
end | ||
|
||
def java_method_arity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't totally get what you're doing in this method and why. Mind leaving a comment explaining it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I explained it the following way in the new commit message, but would you prefer a comment in the code?
The workaround essentially makes use of Java's introspection to figure out matching methods (which could be more than one partly because Java supports multiple overloads, and partly because JRuby introduces aliases to make method names look more Rubyesque). If there is only a single match, we can use that methods arity directly instead of the default -1 arity.
JRuby apparently only supports #arity (and not #parameters) for Java proxy methods (see jruby/jruby#2817), but verifying doubles in RSpec use #parameters to infer required method parameters, and thus doesn't work for Java classes. Given that this issue affects every release of JRuby so far with support for #parameters, it seemed reasonable to replace the previous workaround that only affected JRuby 1.7.0 through 1.7.19.
JRuby apparently didn't even properly support #arity for Java proxy methods before 9.0.3.0 (see jruby/jruby@013e288), so this implements a workaround for these versions. The workaround essentially makes use of Java's introspection to figure out matching methods (which could be more than one partly because Java supports multiple overloads, and partly because JRuby introduces aliases to make method names look more Rubyesque). If there is only a single match, we can use that methods arity directly instead of the default -1 arity. One could in theory do better than -1 for various overload sets with multiple methods in them as well, but we have to draw a line somewhere. Given that this issue also impacts JRuby 1.7.x in 1.8-mode, it would require guards around all the #parameters calls to integrate it into the existing workaround, and thus it is implemented as a separate workaround.
a232b2b
to
ccdce45
Compare
Thanks for the review @myronmarston. I think I could have done a better job explaining the problem in the PR description, so let me make another attempt. I wanted to use an instance double for a Java proxy in JRuby. For those not familiar with JRuby, you can freely interface with any standard Java class through an automatically generated proxy. Briefly, you can write However, the implementation of method signature information for such proxies are not stellar. Until 9.0.3.0 (and jruby/jruby@013e288),
Given that I was on JRuby 9.1.13.0, which has a working |
ccdce45
to
02a2193
Compare
Thanks @grddev! |
|
||
def classify_parameters | ||
super | ||
return unless @method.arity == -1 && @method.owner.respond_to?(:java_class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is breaking the build for rspec/rspec-expectations, see https://travis-ci.org/rspec/rspec-expectations/jobs/351862257
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see now that the method is supposed to work for Proc as well as Method, and #owner
isn't available for Proc. The easiest way to handle this would simply to add @method.respond_to?(:owner)
to the referenced early return. This will effectively disable the workaround for Proc, while retaining it for Method.
While the same problem exists for Proc, and you could observe it using the following construction,
Java::JavaLang::String.new.method(:char_at).to_proc.arity # => -1
I can't think of a workaround that would address this case, as I'm not aware of any JRuby API that allows you to figure out if a Proc is a direct wrapper around a Java method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add the proc fix for now, and add a pending spec demonstrating the problem for the scenario described, this will document our knowledge on this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened a PR with the proposed fix in #347
JRuby apparently only supports #arity (and not #parameters) for Java proxy methods (see jruby/jruby#2817), but verifying doubles in RSpec use #parameters to infer required method parameters, and thus doesn't work for Java classes.
This extends the previously existing JRuby workaround that returns an empty list from #parameters so that it fallbacks to the legacy method of using #arity.
In addition, it turns out that the #arity method is broken for Java proxy methods in JRuby 1.7.x. While JRuby 1.7 has been sunset, the test suite still uses it, and thus this introduces another workaround to account for that.
This looks up the corresponding JavaMethod overloads, and if there is only one, retrieves the arity from that one instead. In the presence of multiple overloads, -1 is returned.
While this obviously doesn't cover all cases, it does cover the cases the previous workaround covered, and also allows using verifying doubles for many Java classes in JRuby.