Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 9e99b0d9c3b4
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: b45f4fea86c0
Choose a head ref
  • 2 commits
  • 2 files changed
  • 1 contributor

Commits on Aug 23, 2015

  1. Copy the full SHA
    46252e1 View commit details
  2. [ji] improved more-specific type matching when matching Java methods

    will now account for all parameter types in a method signature and takes
    last arg proc matching into account - outcomes should be more predictable
    
    as noted (#2865 (comment)) the selection has been still subject to non-deterministic behavior (depending on reflected method orded) esp. for cases where last argument is a proc to be matched as an interface impl
    
    fixes #2865
    kares committed Aug 23, 2015
    Copy the full SHA
    b45f4fe View commit details
Showing with 157 additions and 20 deletions.
  1. +66 −20 core/src/main/java/org/jruby/java/dispatch/CallableSelector.java
  2. +91 −0 spec/java_integration/interfaces/java8_methods_spec.rb
86 changes: 66 additions & 20 deletions core/src/main/java/org/jruby/java/dispatch/CallableSelector.java
Original file line number Diff line number Diff line change
@@ -172,7 +172,8 @@ private static <T extends ParameterTypes> T findMatchingCallableForArgs(final Ru
final int procArity;
if ( lastArg instanceof RubyProc ) {
final Method implMethod; final int last = msTypes.length - 1;
if ( last >= 0 && msTypes[last].isInterface() && ( implMethod = getFunctionalInterfaceMethod(msTypes[last]) ) != null ) {
if ( last >= 0 && msTypes[last].isInterface() &&
( implMethod = getFunctionalInterfaceMethod(msTypes[last]) ) != null ) {
mostSpecificArity = implMethod.getParameterTypes().length;
}
procArity = ((RubyProc) lastArg).getBlock().arity().getValue();
@@ -181,34 +182,37 @@ private static <T extends ParameterTypes> T findMatchingCallableForArgs(final Ru
procArity = Integer.MIN_VALUE;
}

OUTER: for ( int c = 1; c < size; c++ ) {
/* OUTER: */
for ( int c = 1; c < size; c++ ) {
final T candidate = candidates.get(c);
final Class<?>[] cTypes = candidate.getParameterTypes();

// TODO still need to handle var-args better Class<?> lastType;
for ( int i = 0; i < msTypes.length; i++ ) {
final Class<?> msType = msTypes[i], cType = cTypes[i];
if ( msType != cType && msType.isAssignableFrom(cType) ) {
mostSpecific = candidate; msTypes = cTypes;
ambiguous = false; continue OUTER;
}

final boolean lastArgProc = procArity != Integer.MIN_VALUE;
final Boolean moreSpecific = moreSpecificTypes(msTypes, cTypes, lastArgProc);
if ( (Object) moreSpecific == Boolean.TRUE ) {
mostSpecific = candidate; msTypes = cTypes;
ambiguous = false; continue /* OUTER */;
}
// none more specific; check for ambiguities
for ( int i = 0; i < msTypes.length; i++ ) {
final Class<?> msType = msTypes[i], cType = cTypes[i];
if ( msType == cType || msType.isAssignableFrom(cType) || cType.isAssignableFrom(msType) ) {
ambiguous = false; continue OUTER;
}
else if ( cType.isPrimitive() && msType.isAssignableFrom(getBoxType(cType)) ) {
ambiguous = false; continue OUTER;
}
else {
ambiguous = true;
else { // if ( (Object) moreSpecific == Boolean.FALSE ) {
// none more specific; check for ambiguities
for ( int i = 0; i < msTypes.length; i++ ) {
final Class<?> msType = msTypes[i], cType = cTypes[i];
if ( msType == cType || msType.isAssignableFrom(cType) || cType.isAssignableFrom(msType) ) {
ambiguous = false; break; // continue OUTER;
}
else if ( cType.isPrimitive() && msType.isAssignableFrom(getBoxType(cType)) ) {
ambiguous = false; break; // continue OUTER;
}
else {
ambiguous = true;
}
}
}

// special handling if we're dealing with Proc#impl :
if ( procArity != Integer.MIN_VALUE ) { // lastArg instanceof RubyProc
if ( lastArgProc ) { // lastArg instanceof RubyProc
// cases such as (both ifaces - differ in arg count) :
// java.io.File#listFiles(java.io.FileFilter) ... accept(File)
// java.io.File#listFiles(java.io.FilenameFilter) ... accept(File, String)
@@ -287,6 +291,48 @@ else if ( procArity < 0 && methodArity >= -(procArity + 1) ) { // *splat that fi
return method;
}

private static Boolean moreSpecificTypes(final Class[] msTypes, final Class[] cTypes,
final boolean lastArgProc) {

final int last = msTypes.length - 1;
int moreSpecific = 0; Class<?> msType, cType;
for ( int i = 0; i < last; i++ ) {
msType = msTypes[i]; cType = cTypes[i];
if ( msType == cType ) ;
else if ( msType.isAssignableFrom(cType) ) {
moreSpecific++; /* continue; */
}
else if ( cType.isAssignableFrom(msType) ) {
moreSpecific--; /* continue; */
}
/* return false; */
}

if ( last >= 0 ) { // last argument :
msType = msTypes[last]; cType = cTypes[last];
if ( lastArgProc ) {
if ( cType.isAssignableFrom(RubyProc.class) ) {
if ( ! msType.isAssignableFrom(RubyProc.class) ) moreSpecific++;
// return moreSpecific > 0;
}
else {
// NOTE: maybe this needs some implMethod arity matching here?
return null; // interface matching logic (can not decide)
}
}
else {
if ( msType == cType ) ;
else if ( msType.isAssignableFrom(cType) ) {
moreSpecific++;
}
else if ( cType.isAssignableFrom(msType) ) {
moreSpecific--;
}
}
}
return moreSpecific > 0 ? Boolean.TRUE : Boolean.FALSE;
}

private static <T extends ParameterTypes> T findMatchingCallableForArgsFallback(final Ruby runtime,
final T[] methods, final IRubyObject... args) {
T method = findCallable(methods, Exact, args);
91 changes: 91 additions & 0 deletions spec/java_integration/interfaces/java8_methods_spec.rb
Original file line number Diff line number Diff line change
@@ -20,6 +20,47 @@
src = <<-JAVA
public class Java8Implemtor implements Java8Interface {
public String bar() { return getClass().getSimpleName(); }
// re-using the same class for proc-impl testing
public static Object withConsumerCall(Integer i, java.util.function.IntConsumer c) {
c.accept(i * 10); return i + 1;
}
public static boolean withPredicateCall(Object obj, java.util.function.Predicate<Object> p) {
// TODO following line fails (obviously due test method in Ruby) :
// ArgumentError: wrong number of arguments (1 for 2)
// org/jruby/gen/InterfaceImpl817905333.gen:13:in `test'
return p.test(obj);
}
public static String ambiguousCall1(String str, java.util.Map<?, ?> map) {
return "ambiguousWithMap";
}
public static String ambiguousCall1(String str, java.util.function.Consumer<StringBuilder> c) {
StringBuilder builder = new StringBuilder(str);
c.accept(builder.append("ambiguousWithConsumer")); return builder.toString();
}
public static String ambiguousCall2(CharSequence str, java.util.function.Consumer<StringBuilder> c) {
StringBuilder builder = new StringBuilder(str);
c.accept(builder.append("ambiguousWithConsumer")); return builder.toString();
}
public static String ambiguousCall2(String str, java.util.Map<?, ?> map) {
return "ambiguousWithMap";
}
public static String ambiguousCall3(CharSequence str, java.util.function.Consumer<StringBuilder> c) {
StringBuilder builder = new StringBuilder(str);
c.accept(builder.append("ambiguousWithConsumer")); return builder.toString();
}
public static String ambiguousCall3(String str, java.util.Map<?, ?> map) {
return "ambiguousWithMap";
}
public static String ambiguousCall3(String str, java.util.function.DoubleBinaryOperator op) {
return "ambiguousWithBinaryOperator";
}
}
JAVA
files << (file = "#{@tmpdir}/Java8Implemtor.java"); File.open(file, 'w') { |f| f.print(src) }
@@ -67,6 +108,56 @@
expect(impl.java_send(:foo, [ java.lang.Object ], 11)).to eq("11foo Java8Implemtor")
end

it "works with java.util.function-al interface with proc impl" do
expect( Java::Java8Implemtor.withConsumerCall(1) do |i|
expect(i).to eql 10
end ).to eql 2

pending "TODO: Predicate#test won't work as it collides with Ruby method"

ret = Java::Java8Implemtor.withPredicateCall([ ]) { |obj| obj.empty? }
expect( ret ).to be true
ret = Java::Java8Implemtor.withPredicateCall('x') { |obj| obj.empty? }
expect( ret ).to be false
end

it "does not consider Map vs func-type Consumer ambiguous" do
output = with_stderr_captured do # exact match should not warn :
ret = Java::Java8Implemtor.ambiguousCall1('') do |c|
c.append('+proc')
end
expect( ret ).to eql "ambiguousWithConsumer+proc"
end
expect( output.index('ambiguous') ).to be nil

output = with_stderr_captured do # exact match should not warn :
ret = Java::Java8Implemtor.ambiguousCall2('') do |c|
c.append('+proc')
end
expect( ret ).to eql "ambiguousWithConsumer+proc"
end
expect( output.index('ambiguous') ).to be nil

output = with_stderr_captured do # exact match should not warn :
ret = Java::Java8Implemtor.ambiguousCall3('') do |c|
c.append('+proc')
end
expect( ret ).to eql "ambiguousWithConsumer+proc"
end
expect( output.index('ambiguous') ).to be nil
end

def with_stderr_captured
stderr = $stderr; require 'stringio'
begin
$stderr = StringIO.new
yield
$stderr.string
ensure
$stderr = stderr
end
end

def javac_compile(files)
compiler = javax.tools.ToolProvider.getSystemJavaCompiler
fmanager = compiler.getStandardFileManager(nil, nil, nil)