Cannot resolve public generic method from package-private base class

Description

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

    • Invoke B.M()

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:

Environment

None

Activity

Show:
import
June 18, 2016, 7:19 PM

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.

Michal Ruzicka
September 23, 2016, 7:08 PM

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.

Alex Miller
September 23, 2016, 9:00 PM

Hey Michal,

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)

Michal Ruzicka
September 27, 2016, 2:56 PM

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:

src/jvm/clojure/lang/Compiler.java

  • 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

Stuart Sierra
April 14, 2017, 10:37 PM

CLJ-126 describes a similar issue on Java 5.

Assignee

Unassigned

Reporter

Stuart Sierra

Labels

Approval

Triaged

Patch

Code and Test

Affects versions

Priority

Minor
Configure