Bug 546122 - Eclipse fails to compile valid code with overwritten default method
Summary: Eclipse fails to compile valid code with overwritten default method
Status: NEW
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.11   Edit
Hardware: PC Windows 7
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: JDT-Core-Inbox CLA
QA Contact:
URL:
Whiteboard: stalebug
Keywords:
Depends on:
Blocks:
 
Reported: 2019-04-04 12:17 EDT by Adrodoc 55 CLA
Modified: 2023-05-08 12:11 EDT (History)
1 user (show)

See Also:


Attachments
a screenshot of the bug (18.22 KB, image/png)
2019-04-04 12:17 EDT, Adrodoc 55 CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Adrodoc 55 CLA 2019-04-04 12:17:24 EDT
Created attachment 278148 [details]
a screenshot of the bug

Eclipse fails to compile valid code with overwritten default method.

Steps to reproduce:
* git clone https://github.com/Adrodoc/eclipse-compiler-bug-default-method.git
* Import existing project into eclipse
* You should see a compiler error in file E.java (Type mismatch: cannot convert from Object to String)
* Run gradlew compileJava to confirm that gradle can compile successfully

I am using, but I suspect that the non-enterprise version has the same issue.

Eclipse IDE for Enterprise Java Developers.

Version: 2019-03 (4.11.0)
Build id: 20190314-1200
Comment 1 Stephan Herrmann CLA 2019-04-04 17:16:40 EDT
Thanks for the example. Fortunately it doesn't require gradle nor any dependencies to see that javac and ecj differ.

While it's far from trivial to see how JLS classifies this example, I can see that ecj accepts the program if only D is changed from
    public interface D extends C, A { }
to
    public interface D extends B, A { }
which looks inconsistent indeed.

Several sentences in JLS 9.4.1 distinguish between superinterfaces in general and *direct* superinterfaces. Introducing C breaks the direct relationship between D and B. But still B.method() is a *member* of D's direct superinterface C (via inheritance - see JLS 9.2).

We should check our implementation if somewhere it checks for method *being declared* in a direct superinterface, where instead we should check for *being member of* a direct superinterface.
Comment 2 Stephan Herrmann CLA 2019-04-04 17:29:22 EDT
Ever since I first saw this specification I keep tripping over this circularity:

 - in 9.4.1. we want to see that C does not inherit A.method, because that one is "overridden from C" by B.method

 - in 9.4.1.1 we want to see the B.method "overrides from C" A.method, which requires that A.method is not inherited

So, in order to be overridden the method must not be inherited, but in order to be not inherited it must be overridden in the first place?

Once we guess that one method overrides the other, all seems fine. Guessing the opposite doesn't work because of the other constraints in 9.4.1.1.
Comment 3 Adrodoc 55 CLA 2019-04-04 17:38:42 EDT
I haven't read the specification, but I assume it should work kind of like a topological ordering where the lowest declaration of the method wins and if there are multiple lowest declarations you get a compiler error telling you to manually override the method.
Comment 4 Stephan Herrmann CLA 2019-04-04 17:44:14 EDT
(In reply to Adrodoc 55 from comment #3)
> I haven't read the specification, but I assume it should work kind of like a
> topological ordering where the lowest declaration of the method wins and if
> there are multiple lowest declarations you get a compiler error telling you
> to manually override the method.

If you can prove that that's what the spec defines this would be great help ;p
Comment 5 Adrodoc 55 CLA 2019-04-04 17:47:43 EDT
oof, I can take a look at it tomorrow, but since I have never read the spec you probably have better chances than I do :)
Comment 6 Adrodoc 55 CLA 2019-04-05 05:04:38 EDT
(In reply to Stephan Herrmann from comment #2)
> Ever since I first saw this specification I keep tripping over this
> circularity:
> 
>  - in 9.4.1. we want to see that C does not inherit A.method, because that
> one is "overridden from C" by B.method
> 
>  - in 9.4.1.1 we want to see the B.method "overrides from C" A.method, which
> requires that A.method is not inherited
> 
> So, in order to be overridden the method must not be inherited, but in order
> to be not inherited it must be overridden in the first place?
> 
> Once we guess that one method overrides the other, all seems fine. Guessing
> the opposite doesn't work because of the other constraints in 9.4.1.1.

So I took a look at the specification at (https://docs.oracle.com/javase/specs/jls/se12/html/jls-9.html#jls-9.4.1).
I think the rules in 9.4.1 specify the exact behaviour I expected and I don't see a circularity there. Also I realised, that we don't need class E, because the mistake actually occurs in class/interface D.

JLS 9.4.1 states:
> An interface I inherits from its direct superinterfaces all abstract and default methods m for which all of the following are true:
> 
> * m is a member of a direct superinterface, J, of I.
> 
> * No method declared in I has a signature that is a subsignature (§8.4.2) of the signature of m.
> 
> * There exists no method m' that is a member of a direct superinterface, J', of I (m distinct from m', J distinct from J'), such that m' overrides from J' the declaration of the method m.

JLS 9.4.1.1 states:

> An instance method mI declared in or inherited by interface I, overrides from I another instance method mJ declared in interface J, iff all of the following are true:
> 
> * I is a subinterface of J.
> 
> * I does not inherit mJ.
> 
> * The signature of mI is a subsignature (§8.4.2) of the signature of mJ.
> 
> * mJ is public.

For reference here are the classes/interfaces:

public interface A {
  default Object method() {
    return null;
  }
}

public interface B extends A {
  @Override
  default String method() {
    return "";
  }
}

public interface C extends B {}

public class D implements C, A {
  {
    String method = method();
  }
}

So let's break it down for our example:

Interface A declares method mA (Object method()).
Interface B declares method mB (String method()).

According to JLS 9.4.1 we now have to check whether B inherits mA:
Check 1 and 3 pass, but not check 2, because mB declared in B has a subsignature of mA.
Therefore B does not inherit mA which means that mA does not become a member of B.

According to JLS 9.4.1.1 we then have to check whether mB overrides from B the method mA:
All four checks pass, in particular the second one: B does not inherit mA as we just found out by doing the checks defined in JLS 9.4.1.

For interface C which extends B it's really easy:
According to JLS 9.4.1 it inherits mB, because mB is a member of the direct superintererface B, but it does not inherit mA, because mA is not a member of B, because B does not inherit mA from A.
Therefore mB becomes a member of C.

Then we have class D which implements C and A:
Now does D inherit mA which is a member of it's direct superinterface A, or does D inherit mB which is a member of it's direct superinterface B?
Here the third check defined in 9.4.1 comes into play: D does not inherit mA because there exists a method mB that is a member of the direct superinterface C, such that mB overrides from C the declaration of mA.

But wait a second we didn't show yet, that mB overrides from C the method mA declared in A. We only showed that mB overrides from B the method mA declared in A, so let's do that too:
1. C is a subinterface of A: true
2. C does not inherit mA: true, because mA is not a member of any direct superinterface of C and even if it were the third check of JLS 9.4.1 would prevent C from inheriting mA as long as mB is a member of a direct super interface of C
3. The signature of mB is a subsignature of the signature of mA: true
4. mA is public: true

There you go, I hope this qualifies as a proof, so that i can end this with:
quod erat demonstrandum.
Comment 7 Eclipse Genie CLA 2021-03-26 11:38:33 EDT
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet.

If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.
Comment 8 Adrodoc 55 CLA 2021-04-03 14:35:36 EDT
I just tested this on a fresh installation of Eclipse (java-2021-03) and this bug still occurs just as when I originally reported it.
Comment 9 Eclipse Genie CLA 2023-05-08 12:11:33 EDT
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet.

If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.