Method invocation applicability rules confusion

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Method invocation applicability rules confusion

Chris Dennis
Hi All,

I’ve hit an interesting issue with an API that I am responsible for whereby the equivalent to the following does not compile:

public class TargetTypingWeirdness {

  public static void test() {
    Consumer<String> stringConsumer = System.out::println;

    f(stringConsumer, o -> o.toString());
  }

  static <T> Runnable f(Consumer<T> c, T t) {
    return () -> c.accept(t);
  }

  static <T> Consumer<Object> f(Consumer<T> c, Function<Object, T> ft) {
    return o -> c.accept(ft.apply(o));
  }
}

with:

TargetTypingWeirdness.java:9: error: reference to f is ambiguous
    f(stringConsumer, o -> o.toString());
    ^
  both method <T#1>f(Consumer<T#1>,T#1) in TargetTypingWeirdness and method <T#2>f(Consumer<T#2>,Function<Object,T#2>) in TargetTypingWeirdness match
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>f(Consumer<T#1>,T#1)
    T#2 extends Object declared in method <T#2>f(Consumer<T#2>,Function<Object,T#2>)
TargetTypingWeirdness.java:9: error: incompatible types: cannot infer type-variable(s) T
    f(stringConsumer, o -> o.toString());
     ^
    (argument mismatch; String is not a functional interface)
  where T is a type-variable:
    T extends Object declared in method <T>f(Consumer<T>,T)
2 errors

I’m trying to figure out if this is an allowed behavior under the spec, or a bug in javac?

Any help greatly appreciated,

Chris
Reply | Threaded
Open this post in threaded view
|

Re: Method invocation applicability rules confusion

Maurizio Cimadamore
This is a tricky one - I'll try to explain what's happening :-)

FIrst, let's leave lambdas on the side - this has nothing to do with it.
And also simplify the program as follows (I've also alpha-renamed the
type vars):

static <T> void f(Consumer<T> c, T o) { }

static <Z> void f(Consumer<Z> c, String s) { }


And consider the following invocation:

f(null, null)

Now, looking at the available signatures, one might be tempted to
conclude that the second signature is most specific. But that's not how
the rules in JLS 15.12.2.5 work  - those rule say that we need to do two
applicability tests:

1) is f(Consumer<T>, T) applicable given argument types { Consumer<Z>,
String } ?
2) is f(Consumer<Z>, String) applicable given argument types {
Consumer<T>, T } ?

If the answer is yes to either (1) or (2), that that's the most specific
method; if (1) and (2) are both true/false then the callsite is ambiguous.

So, let's process the two questions:

For (1) we have the following applicability tests (note that _only_ Z
acts as an an inference variable in this test)

Consumer<T> <: Consumer<Z>
T <: String

now, no matter what we infer for Z, T<: String is always going to be
false. So (1) is not satisfied. Let's move on to (2).

For (2) we have the following applicability tests (this time _only_ T
acts as an inference variable)

Consumer<Z> <: Consumer<T>
String <: T

This looks more interesting. From the first constraint we derive:

Z = T

But this means that, if we incorporate bounds,

String <: T, T = Z --> String <: Z

Which is, again, false.

So, unfortunately, both applicability tests (1) and (2) fails, so
neither method is more specific. Hence the ambiguity error.

Cheers
Maurizio




On 08/12/17 17:34, Chris Dennis wrote:

> Hi All,
>
> I’ve hit an interesting issue with an API that I am responsible for whereby the equivalent to the following does not compile:
>
> public class TargetTypingWeirdness {
>
>    public static void test() {
>      Consumer<String> stringConsumer = System.out::println;
>
>      f(stringConsumer, o -> o.toString());
>    }
>
>    static <T> Runnable f(Consumer<T> c, T t) {
>      return () -> c.accept(t);
>    }
>
>    static <T> Consumer<Object> f(Consumer<T> c, Function<Object, T> ft) {
>      return o -> c.accept(ft.apply(o));
>    }
> }
>
> with:
>
> TargetTypingWeirdness.java:9: error: reference to f is ambiguous
>      f(stringConsumer, o -> o.toString());
>      ^
>    both method <T#1>f(Consumer<T#1>,T#1) in TargetTypingWeirdness and method <T#2>f(Consumer<T#2>,Function<Object,T#2>) in TargetTypingWeirdness match
>    where T#1,T#2 are type-variables:
>      T#1 extends Object declared in method <T#1>f(Consumer<T#1>,T#1)
>      T#2 extends Object declared in method <T#2>f(Consumer<T#2>,Function<Object,T#2>)
> TargetTypingWeirdness.java:9: error: incompatible types: cannot infer type-variable(s) T
>      f(stringConsumer, o -> o.toString());
>       ^
>      (argument mismatch; String is not a functional interface)
>    where T is a type-variable:
>      T extends Object declared in method <T>f(Consumer<T>,T)
> 2 errors
>
> I’m trying to figure out if this is an allowed behavior under the spec, or a bug in javac?
>
> Any help greatly appreciated,
>
> Chris

Reply | Threaded
Open this post in threaded view
|

Re: Method invocation applicability rules confusion

Chris Dennis
Forgive me, it’s too long since I was at university, and then I was a physicist, so I just pretended to be good at mathematics/computer science. My intuitive understanding says that f(Consumer<T>, T>) shouldn't be considered applicable given the argument types (as per 15.12.2.3 https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.3).  I’ve tried (and will continue to try) to work through the logic of that section and the accompanying details in Chapter 18, but if you could confirm that it is applicable then that would be great (at least then I’d know that what I’m doing is an academic exercise, and that I should end up proving the method is applicable).

Thanks,

Chris


On Dec 8, 2017, at 4:39 PM, Maurizio Cimadamore <[hidden email]> wrote:

This is a tricky one - I'll try to explain what's happening :-)

FIrst, let's leave lambdas on the side - this has nothing to do with it. And also simplify the program as follows (I've also alpha-renamed the type vars):

static <T> void f(Consumer<T> c, T o) { }

static <Z> void f(Consumer<Z> c, String s) { }


And consider the following invocation:

f(null, null)

Now, looking at the available signatures, one might be tempted to conclude that the second signature is most specific. But that's not how the rules in JLS 15.12.2.5 work  - those rule say that we need to do two applicability tests:

1) is f(Consumer<T>, T) applicable given argument types { Consumer<Z>, String } ?
2) is f(Consumer<Z>, String) applicable given argument types { Consumer<T>, T } ?

If the answer is yes to either (1) or (2), that that's the most specific method; if (1) and (2) are both true/false then the callsite is ambiguous.

So, let's process the two questions:

For (1) we have the following applicability tests (note that _only_ Z acts as an an inference variable in this test)

Consumer<T> <: Consumer<Z>
T <: String

now, no matter what we infer for Z, T<: String is always going to be false. So (1) is not satisfied. Let's move on to (2).

For (2) we have the following applicability tests (this time _only_ T acts as an inference variable)

Consumer<Z> <: Consumer<T>
String <: T

This looks more interesting. From the first constraint we derive:

Z = T

But this means that, if we incorporate bounds,

String <: T, T = Z --> String <: Z

Which is, again, false.

So, unfortunately, both applicability tests (1) and (2) fails, so neither method is more specific. Hence the ambiguity error.

Cheers
Maurizio




On 08/12/17 17:34, Chris Dennis wrote:
Hi All,

I’ve hit an interesting issue with an API that I am responsible for whereby the equivalent to the following does not compile:

public class TargetTypingWeirdness {

  public static void test() {
    Consumer<String> stringConsumer = System.out::println;

    f(stringConsumer, o -> o.toString());
  }

  static <T> Runnable f(Consumer<T> c, T t) {
    return () -> c.accept(t);
  }

  static <T> Consumer<Object> f(Consumer<T> c, Function<Object, T> ft) {
    return o -> c.accept(ft.apply(o));
  }
}

with:

TargetTypingWeirdness.java:9: error: reference to f is ambiguous
    f(stringConsumer, o -> o.toString());
    ^
  both method <T#1>f(Consumer<T#1>,T#1) in TargetTypingWeirdness and method <T#2>f(Consumer<T#2>,Function<Object,T#2>) in TargetTypingWeirdness match
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>f(Consumer<T#1>,T#1)
    T#2 extends Object declared in method <T#2>f(Consumer<T#2>,Function<Object,T#2>)
TargetTypingWeirdness.java:9: error: incompatible types: cannot infer type-variable(s) T
    f(stringConsumer, o -> o.toString());
     ^
    (argument mismatch; String is not a functional interface)
  where T is a type-variable:
    T extends Object declared in method <T>f(Consumer<T>,T)
2 errors

I’m trying to figure out if this is an allowed behavior under the spec, or a bug in javac?

Any help greatly appreciated,

Chris


Reply | Threaded
Open this post in threaded view
|

Re: Method invocation applicability rules confusion

Maurizio Cimadamore

Note that in your original example your second argument is:

o -> o.toString()

Whenever you have a lambda (implicit, or explicit) and the target type is some inference variable (like T in this case), the lambda is always considered potentially applicable (as per 15.12.2.1).

Then, since this is an implicit lambda - implicit lambda don't do much in terms of ruling out applicable candidates. This is called pertinence to applicability - see 15.12.2.2. An implicit lambda is NOT pertinent to applicability, so it doesn't contribute to overload resolution.

Hence, both methods in your example are applicable.

Maurizio


On 11/12/17 14:59, Chris Dennis wrote:
Forgive me, it’s too long since I was at university, and then I was a physicist, so I just pretended to be good at mathematics/computer science. My intuitive understanding says that f(Consumer<T>, T>) shouldn't be considered applicable given the argument types (as per 15.12.2.3 https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.3).  I’ve tried (and will continue to try) to work through the logic of that section and the accompanying details in Chapter 18, but if you could confirm that it is applicable then that would be great (at least then I’d know that what I’m doing is an academic exercise, and that I should end up proving the method is applicable).

Thanks,

Chris


On Dec 8, 2017, at 4:39 PM, Maurizio Cimadamore <[hidden email]> wrote:

This is a tricky one - I'll try to explain what's happening :-)

FIrst, let's leave lambdas on the side - this has nothing to do with it. And also simplify the program as follows (I've also alpha-renamed the type vars):

static <T> void f(Consumer<T> c, T o) { }

static <Z> void f(Consumer<Z> c, String s) { }


And consider the following invocation:

f(null, null)

Now, looking at the available signatures, one might be tempted to conclude that the second signature is most specific. But that's not how the rules in JLS 15.12.2.5 work  - those rule say that we need to do two applicability tests:

1) is f(Consumer<T>, T) applicable given argument types { Consumer<Z>, String } ?
2) is f(Consumer<Z>, String) applicable given argument types { Consumer<T>, T } ?

If the answer is yes to either (1) or (2), that that's the most specific method; if (1) and (2) are both true/false then the callsite is ambiguous.

So, let's process the two questions:

For (1) we have the following applicability tests (note that _only_ Z acts as an an inference variable in this test)

Consumer<T> <: Consumer<Z>
T <: String

now, no matter what we infer for Z, T<: String is always going to be false. So (1) is not satisfied. Let's move on to (2).

For (2) we have the following applicability tests (this time _only_ T acts as an inference variable)

Consumer<Z> <: Consumer<T>
String <: T

This looks more interesting. From the first constraint we derive:

Z = T

But this means that, if we incorporate bounds,

String <: T, T = Z --> String <: Z

Which is, again, false.

So, unfortunately, both applicability tests (1) and (2) fails, so neither method is more specific. Hence the ambiguity error.

Cheers
Maurizio




On 08/12/17 17:34, Chris Dennis wrote:
Hi All,

I’ve hit an interesting issue with an API that I am responsible for whereby the equivalent to the following does not compile:

public class TargetTypingWeirdness {

  public static void test() {
    Consumer<String> stringConsumer = System.out::println;

    f(stringConsumer, o -> o.toString());
  }

  static <T> Runnable f(Consumer<T> c, T t) {
    return () -> c.accept(t);
  }

  static <T> Consumer<Object> f(Consumer<T> c, Function<Object, T> ft) {
    return o -> c.accept(ft.apply(o));
  }
}

with:

TargetTypingWeirdness.java:9: error: reference to f is ambiguous
    f(stringConsumer, o -> o.toString());
    ^
  both method <T#1>f(Consumer<T#1>,T#1) in TargetTypingWeirdness and method <T#2>f(Consumer<T#2>,Function<Object,T#2>) in TargetTypingWeirdness match
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>f(Consumer<T#1>,T#1)
    T#2 extends Object declared in method <T#2>f(Consumer<T#2>,Function<Object,T#2>)
TargetTypingWeirdness.java:9: error: incompatible types: cannot infer type-variable(s) T
    f(stringConsumer, o -> o.toString());
     ^
    (argument mismatch; String is not a functional interface)
  where T is a type-variable:
    T extends Object declared in method <T>f(Consumer<T>,T)
2 errors

I’m trying to figure out if this is an allowed behavior under the spec, or a bug in javac?

Any help greatly appreciated,

Chris



Reply | Threaded
Open this post in threaded view
|

Re: Method invocation applicability rules confusion

Chris Dennis
Thank you, I now understand whats going on, I just don’t like it that much.

C’est la vie,

Chris

On Dec 11, 2017, at 11:26 AM, Maurizio Cimadamore <[hidden email]> wrote:

Note that in your original example your second argument is:

o -> o.toString()

Whenever you have a lambda (implicit, or explicit) and the target type is some inference variable (like T in this case), the lambda is always considered potentially applicable (as per 15.12.2.1).

Then, since this is an implicit lambda - implicit lambda don't do much in terms of ruling out applicable candidates. This is called pertinence to applicability - see 15.12.2.2. An implicit lambda is NOT pertinent to applicability, so it doesn't contribute to overload resolution.

Hence, both methods in your example are applicable.

Maurizio


On 11/12/17 14:59, Chris Dennis wrote:
Forgive me, it’s too long since I was at university, and then I was a physicist, so I just pretended to be good at mathematics/computer science. My intuitive understanding says that f(Consumer<T>, T>) shouldn't be considered applicable given the argument types (as per 15.12.2.3 https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.3).  I’ve tried (and will continue to try) to work through the logic of that section and the accompanying details in Chapter 18, but if you could confirm that it is applicable then that would be great (at least then I’d know that what I’m doing is an academic exercise, and that I should end up proving the method is applicable).

Thanks,

Chris


On Dec 8, 2017, at 4:39 PM, Maurizio Cimadamore <[hidden email]> wrote:

This is a tricky one - I'll try to explain what's happening :-)

FIrst, let's leave lambdas on the side - this has nothing to do with it. And also simplify the program as follows (I've also alpha-renamed the type vars):

static <T> void f(Consumer<T> c, T o) { }

static <Z> void f(Consumer<Z> c, String s) { }


And consider the following invocation:

f(null, null)

Now, looking at the available signatures, one might be tempted to conclude that the second signature is most specific. But that's not how the rules in JLS 15.12.2.5 work  - those rule say that we need to do two applicability tests:

1) is f(Consumer<T>, T) applicable given argument types { Consumer<Z>, String } ?
2) is f(Consumer<Z>, String) applicable given argument types { Consumer<T>, T } ?

If the answer is yes to either (1) or (2), that that's the most specific method; if (1) and (2) are both true/false then the callsite is ambiguous.

So, let's process the two questions:

For (1) we have the following applicability tests (note that _only_ Z acts as an an inference variable in this test)

Consumer<T> <: Consumer<Z>
T <: String

now, no matter what we infer for Z, T<: String is always going to be false. So (1) is not satisfied. Let's move on to (2).

For (2) we have the following applicability tests (this time _only_ T acts as an inference variable)

Consumer<Z> <: Consumer<T>
String <: T

This looks more interesting. From the first constraint we derive:

Z = T

But this means that, if we incorporate bounds,

String <: T, T = Z --> String <: Z

Which is, again, false.

So, unfortunately, both applicability tests (1) and (2) fails, so neither method is more specific. Hence the ambiguity error.

Cheers
Maurizio




On 08/12/17 17:34, Chris Dennis wrote:
Hi All,

I’ve hit an interesting issue with an API that I am responsible for whereby the equivalent to the following does not compile:

public class TargetTypingWeirdness {

  public static void test() {
    Consumer<String> stringConsumer = System.out::println;

    f(stringConsumer, o -> o.toString());
  }

  static <T> Runnable f(Consumer<T> c, T t) {
    return () -> c.accept(t);
  }

  static <T> Consumer<Object> f(Consumer<T> c, Function<Object, T> ft) {
    return o -> c.accept(ft.apply(o));
  }
}

with:

TargetTypingWeirdness.java:9: error: reference to f is ambiguous
    f(stringConsumer, o -> o.toString());
    ^
  both method <T#1>f(Consumer<T#1>,T#1) in TargetTypingWeirdness and method <T#2>f(Consumer<T#2>,Function<Object,T#2>) in TargetTypingWeirdness match
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>f(Consumer<T#1>,T#1)
    T#2 extends Object declared in method <T#2>f(Consumer<T#2>,Function<Object,T#2>)
TargetTypingWeirdness.java:9: error: incompatible types: cannot infer type-variable(s) T
    f(stringConsumer, o -> o.toString());
     ^
    (argument mismatch; String is not a functional interface)
  where T is a type-variable:
    T extends Object declared in method <T>f(Consumer<T>,T)
2 errors

I’m trying to figure out if this is an allowed behavior under the spec, or a bug in javac?

Any help greatly appreciated,

Chris