JDK-8077243 : Incorrect reference to method is ambiguous when using lambdas and generics
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 8u40,9
  • Priority: P4
  • Status: Resolved
  • Resolution: Not an Issue
  • OS: windows_8
  • CPU: x86
  • Submitted: 2015-03-30
  • Updated: 2015-11-23
  • Resolved: 2015-10-29
Related Reports
Duplicate :  
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_40"
Java(TM) SE Runtime Environment (build 1.8.0_40-b25)
Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.3.9600]

A DESCRIPTION OF THE PROBLEM :
The code below gives an incorrect compiler error:

public class Ambiguous {
    public static void main(String[] args) {
        consumerIntFunctionTest(data -> {
            Arrays.sort(data);
        }, int[]::new);

        consumerIntFunctionTest(Arrays::sort, int[]::new);
    }

    private static <T> void consumerIntFunctionTest(final Consumer<T> consumer, final IntFunction<T> generator) {

    }

    private static <T> void consumerIntFunctionTest(final Function<T, ?> consumer, final IntFunction<T> generator) {

    }
}

Error:(17, 9) java: reference to consumerIntFunctionTest is ambiguous both method consumerIntFunctionTest(java.util.function.Consumer,java.util.function.IntFunction) in net.tuis.ubench.Ambiguous and method consumerIntFunctionTest(java.util.function.Function,java.util.function.IntFunction) in net.tuis.ubench.Ambiguous match

I believe there should be no error, as all Arrays::sort references are of type void, and none of them return a value. As you can observe, it does work when I explicitly expand the Consumer<T> lambda.

It also works when I explicitely supply the target type, as seen below:

Ambiguous.<int[]>consumerIntFunctionTest(Arrays::sort, int[]::new);



StackOverflow post can be found here: http://stackoverflow.com/questions/29323520/reference-to-method-is-ambiguous-when-using-lambdas-and-generics


REPRODUCIBILITY :
This bug can be reproduced always.


Comments
That the minimal test case: // ---- 8< ---- import java.util.Arrays; import java.util.function.Consumer; import java.util.function.Function; public class X { public static void main(String[] args) { X.<int []>foo(Arrays::sort); } static <T> void foo(Consumer<T> consumer) {} static <T> void foo(Function<T, ?> consumer) { } } fails with an ambiguity error, while the seemingly equivalent one that uses a lambda instead viz: import java.util.Arrays; import java.util.function.Consumer; import java.util.function.Function; public class X { public static void main(String[] args) { X.<int []>foo(t -> { Arrays.sort(t); } ); } static <T> void foo(Consumer<T> consumer) {} static <T> void foo(Function<T, ?> consumer) { } } compiles fine is actually per the specification. Here are the crucial steps: (1) 15.12.2.1 Identify Potentially Applicable Methods: For the method reference case, *both* foo's are potentially applicable, while for the lambda case foo(Function) gets eliminated as NOT being potentially applicable due to this rule: An expression is potentially compatible with a target type according to the following rules: ��� A lambda expression (��15.27) is potentially compatible with a functional interface type (��9.8) if all of the following are true: ��� If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (��15.27.2). Crucially 15.12.2.1 ends with: The definition of potential applicability goes beyond a basic arity check to also take into account the presence and "shape" of functional interface target types. In some cases involving type argument inference, a lambda expression appearing as a method invocation argument cannot be properly typed until after overload resolution. These rules allow the form of the lambda expression to still be taken into account, discarding obviously incorrect target types that might otherwise cause ambiguity errors. Thus the going "beyond a basic arity check to also take into account of the presence and shape ..." part applies only to lambda expressions and not to method references. So we proceed to (2) 15.12.2.2 Phase 1: Identify Matching Arity Methods Applicable by Strict Invocation with *both* foo's for the method reference case and the single foo(Consumer) for the lambda case. At this point, since the method reference and the lambda are not pertinent to applicability and are the only arguments, we proceed to 15.12.2.5 Choosing the Most Specific Method with both foo's in the case of method reference and foo(Consumer) in the case of the lambda. (3) 15.12.2.5 Choosing the Most Specific Method: for the method reference case, neither foo is more specific than the other ====> Ambiguity error. for lambda - there is a single applicable method == most specific method ====> compile fine. So javac behavior upon closer scrutiny stands vindicated and ECJ has an issue here in over-eagerly applying the congruence rules of 15.13.2. Note that if the lambda were to have an expression body as opposed to a block body, the ambiguity error would be the result with javac. This is also spec compliant. In discussions, I learn that it was by design intent at JDK8 time that lambdas get checked for void-value disparity while method references don't. I'll follow up with the spec leads to see if post JDK8 there is reason to accommodate further cases that otherwise appear counter-intuitive at first glance.
29-10-2015

Here is a slightly shorter test case: // ---- 8< ---- import java.util.Arrays; import java.util.function.Consumer; import java.util.function.Function; public class X { public static void main(String[] args) { X.<int []>foo(Arrays::sort); } static <T> void foo(Consumer<T> consumer) {} static <T> void foo(Function<T, ?> consumer) { } } Commenting out just the second foo() makes the program compile fine (good), while commenting out just the first foo() results in (good): /home/srikanth/tmp/X.java:7: error: incompatible types: bad return type in method reference X.<int []>foo(Arrays::sort); ^ void cannot be converted to Object Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output 1 error
29-10-2015

It appears that the difference in eclipse vs javac behavior is due to: Eclipse perhaps over-eagerly applying the congruence rules of 15.13.2 while 15.12.2.1 (Identify Potentially Applicable Methods) speaks of void-value disparity only for lambda expressions. 15.12.2.1 ends with: The definition of potential applicability goes beyond a basic arity check to also take into account the presence and "shape" of functional interface target types. In some cases involving type argument inference, a lambda expression appearing as a method invocation argument cannot be properly typed until after overload resolution. These rules allow the form of the lambda expression to still be taken into account, discarding obviously incorrect target types that might otherwise cause ambiguity errors. It is unclear to me if method references not being checked for void-value disparity is intentional or an oversight of sorts.
27-10-2015

I'm not sure this is a bug - according to this: "15.12.2.2. Phase 1: Identify Matching Arity Methods Applicable by Strict Invocation An argument expression is considered pertinent to applicability for a potentially applicable method m unless it has one of the following forms: An implicitly typed lambda expression (��15.27.1). An inexact method reference expression (��15.13.1). [...] " An overloaded method reference (such as Arrays::sort) is _not_ pertinent to applicability; as such, fuzzy, arity based matching should be used during overload resolution. You get an error when you comment one of the candidate because the error is generate _after_ overload resolution, during invocation type inference. I believe this is the right behavior and a bug in ECJ.
16-07-2015

Problem seems to originate from com.sun.tools.javac.comp.DeferredAttr.DeferredAttrNode.StructuralStuckChecker#visitReference's usage of arity method checker instead of full blown arguments checker. Arity method checker also uses com.sun.tools.javac.comp.Resolve#nilMethodCheck for Most specific method analysis. Don't yet have a plan for fix - Will need to investigate how to fix this.
08-06-2015

I can investigate this one.
28-05-2015

The same compile-time error was seen with 1.9.0-ea-b63. I'll forward the issue to the devs for evaluation.
11-05-2015

Checked this with JDK 8u40 and 8u60 ea b07 (attached test case) and could confirm the result. There is an interesting discussion at stackoverflow about this issue: http://stackoverflow.com/questions/29323520/reference-to-method-is-ambiguous-when-using-lambdas-and-generics Moving this up for further review. ------------------------------------------------------------- Output with JDK 8u40: Ambiguous.java:14: error: reference to consumerIntFunctionTest is ambiguous consumerIntFunctionTest(Arrays::sort, int[]::new); both method <T#1>consumerIntFunctionTest(Consumer<T#1>,IntFunction<T#1>) in Ambiguous and method <T#2>consumerIntFunctionTest(Function<T#2,?>,IntFunction<T#2>) in Ambiguous match where T#1,T#2 are type-variables: T#1 extends Object declared in method <T#1>consumerIntFunctionTest(Consumer<T#1>,IntFunction<T#1>) T#2 extends Object declared in method <T#2>consumerIntFunctionTest(Function<T#2,?>,IntFunction<T#2>) 1 error -------------------------------------------------------------------
08-04-2015