8306854: javac with '-source 7' should honor default methods in implemented Java 8 interfaces

Volker Simonis volker.simonis at gmail.com
Tue Apr 25 15:33:14 UTC 2023


I know that support for `-source 7` was removed in JDK 20 (by
JDK-8173605), but I'd still like to kindly ask you for your opinion
regarding the following issue described at
https://bugs.openjdk.org/browse/JDK-8306854 and copy-pasted below for
your convenience.

Independently on wehter you support this fix, it would be great to
hear if you agree that the described issue is indeed a bug and if the
proposed fix (at https://github.com/openjdk/jdk17u-dev/pull/1296) is a
valid solution for the problem?


Consider the following example:

interface A {
  void foo();

interface B extends A {
  @Override default void foo() { }

class C implements B { }

If we compile `A.java` and `B.java` with `javac -source 8` and
`C.java` with `-source 7` we will get the following error:
$ javac -source 8 A.java
$ javac -source 8 B.java
$ javac -source 7 C.java
C.java:1: error: C is not abstract and does not override abstract
method foo() in A
class C implements B { }

I think this is wrong, because `foo()` is implemented as a default
method in `B`.

The following, slightly simpler example works perfectly fine, although
it also depends on a default method in an implemented interface:

interface D {
  default void bar() { }

class E implements D { }

$ javac -source 8 D.java
$ javac -source 7 E.java

In the second example, `javac` happily accepts the default
implementation of `bar()` from interface `D`.

Interestingly, `ecj` (the [Eclipse Compiler for
compiles both examples without any errors:

$ java -jar ecj-4.27.jar -cp . -source 8 A.java
$ java -jar ecj-4.27.jar -cp . -source 8 B.java
$ java -jar ecj-4.27.jar -cp . -source 7 C.java

I think the problem is in
specifically in line 3123:

3123:   if (allowDefaultMethods) {
3124:       MethodSymbol prov = interfaceCandidates(impl.type, absmeth).head;
3125:       if (prov != null && prov.overrides(absmeth, impl, this, true)) {
3126:           implmeth = prov;
3127:       }
3128   }

Here the check for imlementations of `A::foo()` (`absmeth` in the code
snippet above) for class `C` (`impl` in the code snippet above) will
only be performed if `allowDefaultMethods` is set to true, but
`allowDefaultMethods` is globally set to false by `-source 7`.

Instead of relying on the *global* `-source` setting, I think
`firstUnimplementedAbstractImpl()` should rather rely on the class
file version of the corresponing class (i.e. `B` in this case) and if
that class file version is >= 8, it should consider its default method
implementations. I even think that the check for `allowDefaultMethods`
could be completely removed, because if the class dependency `B`
wasn't compiled before, it will be compiled from source and fail for
`-source 7` because it contains a default method. If on the other
hand, the class dependency `B` was already compiled to a class file
with `-source 8` it is OK to use it's default method.

What do you think?

More information about the compiler-dev mailing list