25

JDK is Oracle' JDK 1.8u65 but the problem has been seen with "as low as" 1.8u25 as well.

Here is the full SSCCE:

public final class Foo
{
    private interface X
    {
        default void x()
        {
        }
    }

    private enum E1
        implements X
    {
        INSTANCE,
        ;
    }

    private enum E2
        implements X
    {
        INSTANCE,
        ;
    }

    public static void main(final String... args)
    {
        Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(X::x);
    }
}

This code compiles; but it fails at runtime:

Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception
    at java.lang.invoke.CallSite.makeSite(CallSite.java:341)
    at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307)
    at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297)
    at com.github.fge.grappa.debugger.main.Foo.main(Foo.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class java.lang.Enum; not a subtype of implementation type interface com.github.fge.grappa.debugger.main.Foo$X
    at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:233)
    at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
    at java.lang.invoke.CallSite.makeSite(CallSite.java:302)
    ... 8 more

Fixing it in code is "easy"; in the main method, you just have to:

// Note the <X>
Stream.<X>of(E1.INSTANCE, E2.INSTANCE).forEach(X::x);

EDIT There is in fact a second way, as mentioned in the accepted answer... Replace the method reference with a lambda:

Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x());

So, uh. What happens here? Why does the initial code compile in the first place? I'd have expected the compiler to notice that the method reference was not on anything Enum<?> but on X, but no...

What am I missing? Is this a bug in the compiler? A misunderstanding of mine?

fge
  • 119,121
  • 33
  • 254
  • 329
  • 2
    Perhaps a related `javac` bug: [JDK-8141508](https://bugs.openjdk.java.net/browse/JDK-8141508) although it seems to deal with intersecting types. – Tunaki Nov 25 '15 at 20:15
  • @Tunaki interesting; I'll read through this issue, maybe it's the same... – fge Nov 25 '15 at 20:19
  • 2
    It *is* an intersecting type. The inferred type of `Stream.of(E1.INSTANCE,E2.INSTANCE)` is `Stream&Foo.X>`, according to my Eclipse. – RealSkeptic Nov 25 '15 at 20:22
  • @RealSkeptic How were you able to determine that? My Eclipse Mars 4.5.1 shows me a `Stream extends Enum>>` – Tunaki Nov 25 '15 at 20:33
  • 1
    @Tunaki Easy: you assign it to an unrelated variable, like `int i = Stream.of(...)`, and see what the error message complains about. :-) – RealSkeptic Nov 25 '15 at 20:34
  • 1
    @RealSkeptic Nice, I see! :) There I guess this is the same bug as above. – Tunaki Nov 25 '15 at 20:34
  • @Tunaki agreed; I'd like to add a link to this question to the bug in question but the requirements for having an account on OpenJDK's bug system are blurry at best... *sigh* – fge Nov 25 '15 at 20:39
  • @RealSkeptic a question if you don't mind; Eclipse still uses ECJ instead of plain javac, right? So, does it mean that ECJ shares the same bug? – fge Nov 25 '15 at 20:42
  • 1
    @fge I would think so. The fact that it reports two different types - one when hovering over the `of` or `forEach`, which just says it's a stream of Enums, and one when I use the trick above means that the compiler itself is a bit confused. I think one of these allows the `X::x`, but the other writes byte code that references the wrong type. – RealSkeptic Nov 25 '15 at 20:51

1 Answers1

24

It seems you've hit JDK-8141508, which is indeed a bug of javac when dealing with intersection types and method-references. It is scheduled to be fixed in Java 9.

Quoting a mail from Remi Forax:

javac has trouble with intersection type that are target type of a lambda and method reference, Usually when there is an intersection type, javac substitute it by the first type of the intersection type and add cast when necessary.

Let suppose we have this code,

public class Intersection {
      interface I {
      }
      interface J {
          void foo();
      }

      static <T extends I & J> void bar(T t) {
          Runnable r = t::foo;
      } 

      public static void main(String[] args) {
          class A implements I, J { public void foo() {} }
          bar(new A());
      }
  }

Currently, javac generates a method reference on J::foo with an invokedynamic that takes an I as parameter, hence it fails at runtime. javac should de-sugar t::foo into a lambda that take an I and then add a cast to J like for a call to a method of an intersection type.

So the workaround is to use a lambda instead,

Runnable r = t -> t.foo();

I've already seen this bug somewhere but was not able to find a corresponding bug report in the database :(

In your code, the Stream created by Stream.of(E1.INSTANCE, E2.INSTANCE) is of type Stream<Enum<?>&Foo.X>, which combines all the elements of the bug: intersecting types and method-references.

As noted by Remi Forax, a work-around would be:

Stream.of(E1.INSTANCE, E2.INSTANCE).forEach(x -> x.x());

i.e. using an explicit lambda expression instead of a method-reference.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Yep, this workaround also works (I have tested it before you answered and stumbled upon this very mail as well, after you linked to the bug). Good digging! Thanks! – fge Nov 25 '15 at 20:58
  • I opened an enhancement request on Jetbrains for that: https://youtrack.jetbrains.com/issue/IDEA-148528. I guess a similar request could be opened for Eclipse :) – fge Nov 26 '15 at 08:32
  • @fge Yes I searched their Bugzilla but couldn't find anything. – Tunaki Nov 26 '15 at 08:32
  • 1
    The older bug that Remi Forax didn’t find is [JDK-8058112](https://bugs.openjdk.java.net/browse/JDK-8058112). Maybe the fact that it is already closed as allegedly fixed hindered finding it. Note that the examples of [the related older question](http://stackoverflow.com/q/27031244/2711488) still fail with recent JDKs… – Holger Nov 26 '15 at 12:02