The Clojure compiler cannot resolve a public generic method inherited from a package-private base class.
Instructions to reproduce:
In package P1
Define a package-private class A with generic type parameters
Define a public method M in A using generic types in either its arguments or return value
Define a public class B which extends A
In package P2
Construct an instance of B
This is valid in Java. In Clojure, invoking B.M produces a reflection warning, followed by the error "java.lang.IllegalArgumentException: Can't call public method of non-public class." No amount of type-hinting prevents the warning or the error.
Attachment clj-1243-demo1.tar.gz contains sample code and script to demonstrate the problem.
Examples of Java projects which use public methods in package-private classes:
Comment made by: kstrempel
I ran into the exact same issue with Google's Cloud API's.
Tested it with 1.8 and with 1.9.0-alpha7. Same Problem.
I ran into the same issue. The
fixes the problem for me.
All tests in the project still pass, but this desperately needs a review of someone knowledgeable.
Thanks for looking at it.
1. Please follow the instructions on how to create a patch in the proper format here: http://dev.clojure.org/display/community/Developing+Patches
2. If you can provide some explanation of the changes to aid in review that would be most helpful. Otherwise screeners have to re-engineer your thought processes from scratch.
3. Before getting screened, this change will also need some tests (admittedly not particularly fun to write, but I think it's necessary here)
I've added tests and updated the patch according to the instructions.
Here is some reasoning behind it. Below is an excerpt from the src/jvm/clojure/lang/Compiler.java file:
the condition on line 1462 ensures that the type/class of the target is known
the clojure.lang.Reflector.getMethods() method called on line 1464 returns a list of all public methods of the given name defined for the target type
then the best method to call is selected on lines 1477-1491
if the declaring class of the selected method is not public then an attempt is made to find a public class which is both superclass of the target type and a subclass of the class declaring the selected method - this is implemented in the clojure.lang.Reflector.getDeepestPublicDescendant() method
if such a class is found than it is used instead of the method's declaring class when emitting the byte code for the method call
if no such class is found then an attempt is made to find a compatible method in the public ancestors of the class declaring the selected method
Note that the change may result in a different method being called than prior to the change as demonstrated by the selecting-method-on-nonpublic-interface test. This is IMO an acceptable change as it:
results in better matching (with respect to the argument types) method to be called
makes the method selection in clojure behave in a more similar way to that in java
CLJ-126 describes a similar issue on Java 5.