JDK-8058112 : Invalid BootstrapMethod for constructor/method reference
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 8u20,9
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux
  • CPU: x86_64
  • Submitted: 2014-09-10
  • Updated: 2015-06-04
  • Resolved: 2014-11-25
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u40 b17Fixed 9Fixed
Description
FULL PRODUCT VERSION :
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Linux 3.13.0-24-generic #47-Ubuntu SMP Fri May 2 23:30:00 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux


EXTRA RELEVANT SYSTEM CONFIGURATION :
Also reproducable on windows 7, not tested elsewhere

A DESCRIPTION OF THE PROBLEM :
When trying to apply a map() using a Function over a multi-bounded intersection generic type, the JRE fails with error message
Caused by: java.lang.invoke.LambdaConversionException: Type mismatch for lambda argument 0: interface B is not convertible to interface A



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the main method in the code

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No error message
ACTUAL -
java.lang.invoke.LambdaConversionException: Type mismatch for lambda argument 0: interface B is not convertible to interface A



ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.lang.invoke.LambdaConversionException: Type mismatch for lambda argument 0: interface B is not convertible to interface A


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import static java.util.stream.Collectors.toList;

public class Main {


    public static void main(String[] args) {
        Main main = new Main();
        main.toInfoListError(Arrays.asList(new Base()));
    }

    public <H extends B & A> List<Info> toInfoListError(List<H> list) {
        Comparator<B> byNameComparator = (B b1, B b2) -> b1.getB().compareToIgnoreCase(b2.getB());
        return list.stream().sorted(byNameComparator).map(Info::new).collect(toList());
    }

    public <H extends B & A> List<Info> toInfoListWorks(List<H> list) {
        Comparator<B> byNameComparator = (B b1, B b2) -> b1.getB().compareToIgnoreCase(b2.getB());
        return list.stream().sorted(byNameComparator).map(s -> new Info(s)).collect(toList());
    }
}

interface B {
    public String getB();
}

interface A {
    public long getA();
}

class Info {

    private final long a;
    private final String b;

    <H extends A & B> Info(H h) {
        a = h.getA();
        b = h.getB();
    }
}

class Base implements A, B {

    @Override
    public long getA() {
        return 7L;
    }

    @Override
    public String getB() {
        return "hello";
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Option A:
Swap interfaces: replace <H extends B & A> to <H extends A & B> in method toInfoListError
Order of the interfaces in the definition does not seem to bother the lambda used in method toInfoListWorks() though

Option B:
Use a lambda instead of a Function,  cfr method toInfoListWorks()



Comments
Ultimately, this probably needs a clarified spec for LambdaMetafactory, relaxing the constraints and updating how checkcasts are performed.
08-12-2014

Related bug on StackOverflow: http://stackoverflow.com/questions/27031244/lambdaconversionexception-with-generics-jvm-bug/27037406#27037406 I assume this fix will address it, too.
22-11-2014

The lambda metafactory enforces an invariant that the type of implementation method parameters are a subtype of the corresponding functional interface parameter. When the type of the method reference interface method parameter is an intersection type it gets erased to the LUB, Object if unrelated. Since erased types are used in the bootstrap table, this breaks the invariant and the metafactory reports am error. Without changing the metafactory API there is no way to fix this at the metafactory level, so my proposed change converts method references with intersection types into lambdas, as is done with other complex/problematic method references.
20-11-2014

This one is even funnier, you only need one interface: public class MultipleBoundErasureMethodRef2 { interface A {} interface Foo { <T extends Object & A> void foo(T t); } static <T extends A> void bar(T t) { } public static void main(String[] args) { Foo foo = MultipleBoundErasureMethodRef2::bar; System.out.println(foo); } } I think that the bug lies in AbstractValidatingLambdaMetafactory.isAdaptableTo, the last line should not try to do an isAssignableFrom if toType is an interface. R��mi
10-09-2014

A simpler test case: import java.util.function.Function; public class LambdaTest { interface B { } interface A { } static class Info { <H extends A & B> Info(H h) { } static <H extends A & B> Info info(H h) { return new Info(h); } } public static void main(String[] args) { test(); } // Note the switch in order compared to that on Info static <H extends B & A> void test() { try { Function<H, Info> f1 = _h -> new Info(_h); } catch (Throwable t) { System.out.println("FAILED: _h -> new Info(_h)"); t.printStackTrace(System.out); } try { Function<H, Info> f3 = Info::new; } catch (Throwable t) { System.out.println("FAILED: Info::new"); t.printStackTrace(System.out); } try { Function<H, Info> f1 = _h -> Info.info(_h); } catch (Throwable t) { System.out.println("FAILED: _h -> new Info(_h)"); t.printStackTrace(System.out); } try { Function<H, Info> f2 = Info::info; } catch (Throwable t) { System.out.println("FAILED: Info::info"); t.printStackTrace(System.out); } } } BootstrapMethods: 0: #47 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #48 (Ljava/lang/Object;)Ljava/lang/Object; #49 invokestatic LambdaTest.lambda$test$0:(LLambdaTest$B;)LLambdaTest$Info; #50 (LLambdaTest$B;)LLambdaTest$Info; 1: #47 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #48 (Ljava/lang/Object;)Ljava/lang/Object; #59 newinvokespecial LambdaTest$Info."<init>":(LLambdaTest$A;)V #50 (LLambdaTest$B;)LLambdaTest$Info;
10-09-2014