eclipse-jdt / eclipse.jdt.core

Eclipse Public License 2.0
158 stars 128 forks source link

Eclipse compiler can determine intended varargs method while javac complains method ambiguous #370

Open Madjosz opened 2 years ago

Madjosz commented 2 years ago

Transfer of Bug 578410

Problem

Eclipse compiler can infer primitive varargs overload while javac errors with "reference to method is ambiguous". This leads to nice little surprises when the build pipeline in Jenkins then fails unexpectedly.

Example

public class NonAmbiguousVarargs {

    static void method(int... array) {
        System.out.println("int array");
    }

    static <T> void method(T... array) {
        System.out.println("Object array");
    }

    public static void main(String[] args) {
        method(1);
    }
}

While eclipsec compiles this code without problems, bindind the call to method to the int... overload, javac fails with the following error message

NonAmbiguousVarargs.java:12: error: reference to method is ambiguous
        method(1);
        ^
  both method method(int...) in NonAmbiguousVarargs and method <T>method(T...) in NonAmbiguousVarargs match
  where T is a type-variable:
    T extends Object declared in method <T>method(T...)
1 error

The compiled class will output "int array" in all cases where compilation was successful.

Assuming the behaviour of the javac is correct according to the Java language specification (the code does only compile in JDK 1.5 and 1.6; JDK 1.7 - 20 give the above error) then eclipsec should also give an error on this line.

Tested with

stephan-herrmann commented 2 years ago

Generally, overload resolution happens in phases such when one phase succeeds, the next phase is never consulted. The first phase ("Applicable by Strict Invocation") does not allow an conversions like (un)boxing. At a naive look this could justify why the first method is chosen over the second. However, even the first method is not a "Strict Invocation" but a "Variable Arity Invocation" (the int must still be converted to int[]). In that - 3rd - phase there is no more mentioning of "strict". Ergo, both methods are applicable, and it would require step "15.12.2.5. Choosing the Most Specific Method" to prefer one over the other.

Someone would have to spend time to debug the compiler with the corresonding JLS paragraphs on the table. A key piece will probably be https://docs.oracle.com/javase/specs/jls/se19/html/jls-18.html#jls-18.5.4.

OTOH, overloading is one of the best means to not only confuse compilers but also programmers. Overloading with generic methods likely comes with more complexity then what average users of those methods can handle. Add varargs plus boxing to the mix and you've got one of the hardest riddles the language has to offer. Method names are cheap, the complexity of overloading is not - far from it.

phoenix384 commented 1 year ago

Something like this also happens with

class A {
    public void method(String a, String... b) {
    }
}

class B extends A {
    public void method(String a, String a2, String... b) {
    }
}

when you call the method with something like method("a", "b", "c", "d");

Eclipse wants to use the method in class B while javac is not happy.

stephan-herrmann commented 1 year ago

Eclipse wants to use the method in class B while javac is not happy.

At compliance 1.7 reports the same ambiguity, only since 1.8 the example is accepted.

However, the original issue was about a combination of varargs plus boxing in overloading resolution. Since the newer example has no boxing in the picture please file a new issue. Thanks.

stephan-herrmann commented 1 year ago

Anyone planning to investigate these issues may want to have a look at https://bugs.eclipse.org/bugs/show_bug.cgi?id=457233#c2 where I collected some links to bug fixes of JLS, that may not yet have found their way into ecj.