Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload resolution with method references and function interface specializations for primitive types

Let's say we have a class and an overloaded function:

public class Main {
    static final class A {
    }

    public static String g(ToIntFunction<? extends A> f) {
        return null;
    }

    public static String g(ToDoubleFunction<? extends A> f) {
        return null;
    }
}

and I want to call g with a method reference to a function of type A -> int:

public class Main {
    static final class A {
    }

    public static String g(ToIntFunction<? extends A> f) {
        return null;
    }

    public static String g(ToDoubleFunction<? extends A> f) {
        return null;
    }

    private static int toInt(A x) {
        return 2;
    }

    public static void main(String[] args) {
        ToIntFunction<? extends A> f1 = Main::toInt;
        ToDoubleFunction<? extends A> f2 = Main::toInt;

        g(Main::toInt);
    }
}

This works with javac, but not with eclipse ecj. I submitted a bug report to ecj, but I am not sure if this is an ecj or javac bug and tried to follow the overload resolution algorithm to figure it out. My feeling is that the code should be accepted because it is intuitively reasonable that ToIntFunction is a better match for toInt than ToDoubleFunction. However, my reading of the JLS is that it should be rejected because there is no justification for one being more specific.

I have to admit that I am a little lost in the JLS spec and would appreciate some help. I first wanted to compute the type of Main::double2int, so I looked at 15.13.2. Type of a Method Reference. It does not define the type, but it defines when the type is compatible in different contexts:

A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.

The ground types are ToIntFunction<A> and ToDoubleFunction<A>. toInt returns an int which is assignment compatible to double so I would conclude that the method reference is campatible with ToIntFunction<? extends A> and ToDoubleFunction<? extends A> in invocation context. This can be verified by assinging the method reference to both ToIntFunction<? extends A> and ToDoubleFunction<? extends A> which is accepted in the main function.

Then I looked at overload resolution and found 15.12.2.5. Choosing the Most Specific Method which has a special case for method references to decide which of the two overloads for ToIntFunction or ToDoubleFunction is more specific for the parameter Main::toInt of compile-time declaration A -> int.

A functional interface type S is more specific than a functional interface type T for an expression e if T is not a subtype of S and one of the following is true (where U1 ... Uk and R1 are the parameter types and return type of the function type of the capture of S, and V1 ... Vk and R2 are the parameter types and return type of the function type of T):

...

If e is an exact method reference expression (§15.13.1), then i) for all i (1 ≤ i ≤ k), Ui is the same as Vi, and ii) one of the following is true:

R2 is void.

R1 <: R2.

R1 is a primitive type, R2 is a reference type, and the compile-time declaration for the method reference has a return type which is a primitive type.

R1 is a reference type, R2 is a primitive type, and the compile-time declaration for the method reference has a return type which is a reference type.

The first condition obviously does not match because R1 and R2 are not void.

The two interfaces ToIntFunction and ToDoubleFunction differ only in their return types which are primitive types double and int. For primitive types, the clause "R1 <: R2" is defined in 4.10.1 according to the size of the types. There is no relation between double and int, so this case does not define which type is more specific.

The last two points do not fit either because none of the two functional interfaces have a return value of reference type.

It seems that there is no rule for the case when two functional interfaces return primitives and the code should be rejected as ambiguous. However, javac accepts the code, and I would expect it to do so. So I am wondering if that is a missing point in the JLS.

like image 328
Jens Avatar asked Aug 25 '17 09:08

Jens


1 Answers

For primitive types, the clause "R1 <: R2" is defined in 4.10.1 according to the size of the types. There is no relation between double and int, so this case does not define which type is more specific.

This is not the case; double is, in fact, a supertype of int.

Paragraph 4.10:

The supertypes of a type are obtained by reflexive and transitive closure over the direct supertype relation, written S >₁ T, which is defined by rules given later in this section. We write S :> T to indicate that the supertype relation holds between S and T.

Paragraph 4.10.1:

The following rules define the direct supertype relation among the primitive types:

double >₁ float

float >₁ long

long >₁ int

The supertype relation being a reflexive and transitive closure of the direct supertype relation means that from (double >₁ float) ∧ (float >₁ long) ∧ (long >₁ int) follows double :> int.

like image 173
Anton Avatar answered Nov 02 '22 13:11

Anton