JDK-8314382 : Javac converts to against jls.
  • Type: Bug
  • Component: specification
  • Sub-Component: language
  • Affected Version: 14
  • Priority: P4
  • Status: New
  • Resolution: Unresolved
  • Submitted: 2020-04-09
  • Updated: 2023-08-16
Related Reports
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Windows 10 1903
openjdk 14 2020-03-17
OpenJDK Runtime Environment (build 14+36-1461)
OpenJDK 64-Bit Server VM (build 14+36-1461, mixed mode, sharing)

A DESCRIPTION OF THE PROBLEM :
Javac allows a type conversion which is very reasonable, but which is forbidden by JLS.
This report recommends JLS to be fixed. (But not sure whether it is a bug of JLS or of javac, but surely it is a bug.)
(by the way, why can't I choose Component=specification Subcomponent=language?)

public class MyClass <X extends Runnable> {}

MyClass<? super Runnable> x = null;
MyClass<Runnable> y = x;  //allowed by javac, forbidden by jls.

Yes, this conversion is very reasonable. Should be allowed. But unfortunately, jls is forbidding it.

Jls 5_2 says, in short, a ref-to-ref assignation is allowed basically iff it is a widening reference conversion.
A widening reference conversion exists if the src is subtype of the dst, as specified in 5_1_5.
Subtyping among ref types is defined in 4_10_2. To justify the above assignation, <Runnable> must contain
<CAPTURE>, where CAPTURE is a fresh variable whose lower bound and upper bound both are Runnable.
But the definition of 'containing' in 4_5_1 states that a type only contains itself. Unless jls introduces something like equivalence of types or specifies a condition based on which two apparantly different types can be treated as the same thing, <Runnable> does not contain <CAPTURE>. The conversion is wrong.

javac seems to be regarding MyClass<? super Runnable> and MyClass<Runnable> as the same. Moreover, List<MyClass<? super Runnable>> and List<MyClass<Runnable>> as the same. (Each can be converted into the other.) When do these equations occur?

Whoa! The rule is far from trivial! See another example I've just found!

class Second <X extends Runnable, Y extends X> {}
<U extends Thread> void f() {
    List<Second<U, ? super Thread>> c0 = null;
    List<Second<U, U>> c1 = c0;
    List<Second<U, ? super Thread>> c2 = c1;
}

Surprisingly, c1 = c0 is allowed but c2 = c1 is a copile-time error! What is going on?
4_5_1 says two types have containing relation between them only through "T  and Second<U, U> can be 'T' at the same time, but when c2 = c1, not? Now I am thinking jls is really buggy around this.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
If you are curious about my second example, compile the following and see how it will fail. Then comment the line c2 = c1 out, compile, and see that it will succeeds. No need to execute it.


---------- BEGIN SOURCE ----------
import java.util.*;

public class JlsSux{

	class First <X extends Runnable> {}
	class Second <X extends Runnable, Y extends X> {}


	public static void main(String... args){
		List<First<? super Runnable>> a0 = null;
		List<First<Runnable>> a1 = a0;
		List<First<? super Runnable>> a2 = a1;
	}


	static <U extends Thread> void u(){
		List<Second<U, ? super Thread>> c0 = null;
		List<Second<U, U>> c1 = c0;
		List<Second<U, ? super Thread>> c2 = c1;
	}

}
---------- END SOURCE ----------

FREQUENCY : always



Comments
The observation on Windows 10: JDK 14: Fail. After comment the line c2 = c1 out, the compilation failed. JlsSux.java:19: error: incompatible types: List<JlsSux.Second<U,U>> cannot be converted to List<JlsSux.Second<U,? super Thread>> List<Second<U, ? super Thread>> c2 = c1; ^ where U is a type-variable: U extends Thread declared in method <U>u() 1 error
16-08-2023