Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 562230

Summary: Unexpected cast compile error for generic type argument
Product: [Eclipse Project] JDT Reporter: Andrea Saba <andreasaba>
Component: CoreAssignee: JDT-Core-Inbox <jdt-core-inbox>
Status: CLOSED MOVED QA Contact:
Severity: major    
Priority: P3 CC: simeon.danailov.andreev, stephan.herrmann
Version: 4.15   
Target Milestone: ---   
Hardware: PC   
OS: Windows 10   
Whiteboard: stalebug
Attachments:
Description Flags
the maven project which compiles everywhere except within eclipse none

Description Andrea Saba CLA 2020-04-16 12:47:56 EDT
Created attachment 282479 [details]
the maven project which compiles everywhere except within eclipse

I'm attaching a zip with very little code

I imported it in various version of eclipse with the usual "import / existing maven project"
Eclipse marks a compilation error at line 10

Running:
mvn clean package
(a build configuration within eclipse so exactly the same platform)
I do not get any compiling error

I defined only one jdk in my eclipse and it matches the 1.8 level everywhere
Comment 1 Stephan Herrmann CLA 2020-04-16 15:02:53 EDT
Ecj reports this error:

----------
1. ERROR in /tmp/test/src/main/java/test/Test.java (at line 10)
        Path<String> x = (Path<String>) property;
                         ^^^^^^^^^^^^^^^^^^^^^^^
Cannot cast from Path<Z> to Path<String>
----------
1 problem (1 error)

Base on the declaration <Z extends Comparable<? super Z>> I doubt that this cast is legal.

Compare to this

    	Path<Comparable<?>> property3= null;
        Path<String> x3 = (Path<String>) property3;

Here, javac reports:

Test.java:14: error: incompatible types: Path<Comparable<?>> cannot be converted to Path<String>
        Path<String> x3 = (Path<String>) property3;


Further hint: when you write
    property = x;
javac complains:
error: incompatible types: Path<String> cannot be converted to Path<Z>
        property = x;

If x is not assignable to property (i.e., this is not widening) then it doesn't make much sense to attempt casting (i.e., narrowing) in the opposite direction.

Ergo, I'm not convinced that javac is correct here.
Comment 2 Andrea Saba CLA 2020-04-17 04:42:13 EDT
@stephan.herrmann@berlin.de

Isn't eclipse relying on javac to compile?
Anyway eventually the code that is going to run is the one compiled by the build system that will definitely rely on javac, it may be expected by everyone that what compiles with certain settings with javac compiles with eclipse and viceversa
Comment 3 Stephan Herrmann CLA 2020-04-17 17:31:20 EDT
(In reply to Andrea Saba from comment #2)
> @stephan.herrmann@berlin.de
> 
> Isn't eclipse relying on javac to compile?

No, Eclipse has its own compiler "ecj".

> Anyway eventually the code that is going to run is the one compiled by the
> build system that will definitely rely on javac,

All build systems are free to choose between javac & ecj. Eclipse itself, e.g., is 100% compiled by ecj, not javac. There are numerous others.

> it may be expected by
> everyone that what compiles with certain settings with javac compiles with
> eclipse and viceversa

You may expect, but the better expectation is that both compilers conform to the Java Language Specification (JLS). Any deviation of a compiler from what is specified in JLS is a bug, no matter what's the name of the compiler.

Ergo, blindly trying to mimic javac, would on the one hand be horrendously difficult if you don't know the rules how javac arrives at its solution. But more importantly it would be wrong.

Long story, short: We need to figure out how the rules of https://docs.oracle.com/javase/specs/jls/se14/html/jls-5.html#jls-5.1.6 apply to the given example. No more, no less.

That section, btw, contains an example of an illegal cast / narrowing conversion:

"no narrowing reference conversion exists from ArrayList<String> to ArrayList<Object>, or vice versa, because the type arguments String and Object are provably distinct"

The rules for "provably distinct" (JLS ยง4.5) are, however, quite weak plus a bit vague. I begin to feel that ecj is interpreting too much into this rule, i.e., trying to be too smart, which would be wrong.
Comment 4 Andrea Saba CLA 2020-04-18 06:56:02 EDT
Thanks Stephen, in the meantime I had investigated and found out.
I agree, theoretically every compiler should adhere to the specifications, anyway sometimes specifications may leave some space to interpretation. That is not actually my point: in a real project we have constraints to use a specific jdk/jre and it's extremely advantageous to uniform your ide to the toolchain (to get prompt feedback on your code). Having the possibility to choose the compiler freely would be a great advantage.
Comment 5 Stephan Herrmann CLA 2020-04-18 07:05:33 EDT
(In reply to Andrea Saba from comment #4)
> [...] Having the possibility to
> choose the compiler freely would be a great advantage.

Also in Eclipse you can still invoke javac via the build tool of your choice (ant, maven, gradle ...) *in addition* to what Eclipse natively does, but all IDE functions (editing, completion, navigation, refactoring, type hierarchy, ... you name it) is made possible by tight integration with ecj. This cannot be changed.
Comment 6 Eclipse Genie CLA 2022-04-09 03:25:34 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 7 Andrea Saba CLA 2022-04-09 06:18:46 EDT
(In reply to Eclipse Genie from comment #6)
> 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.

thanks, in the meantime (for this and a good bunch of problems) I decided to go outside and test a few other IDEs and eventually decided to go with a commercial one, so not anymore relevant for me, and could be closed.
Comment 8 Stephan Herrmann CLA 2022-04-09 07:03:13 EDT
(In reply to Andrea Saba from comment #7)
> (In reply to Eclipse Genie from comment #6)
> > 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.
> 
> thanks, in the meantime (for this and a good bunch of problems) I decided to
> go outside and test a few other IDEs and eventually decided to go with a
> commercial one, so not anymore relevant for me, and could be closed.

Looking from 2 years' distance, I found a variant of the example that indicates that my initial assessment was probably wrong, see below. I apologize. 

OTOH, I have spent a LOT of time investigating differences between compilers which turned out to be bugs in javac.

For the future I can only recommend: when encountering a difference between compilers, with no clear bias which one is at fault: users should file bugs against both compilers, so we can compare the analysis by both compiler teams. 

//----
package test;

import java.util.List;
import java.util.Collections;

public class Test {
    public static
    <Z extends Comparable<? super Z>>
    String test(List<Z> in) {
        List<String> x = (List<String>) in;
        return x.get(0);
    }
    
    public static void main(String ... args) {
        List<String> list = Collections.singletonList("hello");
        String string = test(list);
        System.out.println(string);
        Test.<String>test(list); // alternative invocation
    }
}
//----

ecj raises the same "Cannot cast" error, whereas javac accepts with only an "unchecked" warning. What's more, when compiled with javac, the program runs successfully and prints "hello".

Finally, the "alternative invocation" demonstrates that it is possible to *instantiate* the type variable 'Z' to mean 'String'.

Thus my original reasoning applied the wrong concept: this cast is not *narrowing*, it is about assuming how 'Z' may have been instantiated at the call site.

(As an aside: as javac's "unchecked" warning states, the program is inherently unsafe. 
Just add this invocation to main:
  test(Collections.singletonList(13));
and you'll get a ClassCastException from the return statement, which doesn't even declare a cast!!!).


All this is said for posterity, in case someone finds the time to look into this, dig out the JLS section that defines legality of this kind of cast, etc...

I personally don't have plans to work on this, but I will be happy to help anyone driving this forward.
Comment 9 Simeon Andreev CLA 2023-03-14 11:32:45 EDT
Opened GitHub issue: https://github.com/eclipse-jdt/eclipse.jdt.core/issues/837