Community
Participate
Working Groups
I have a Java file which is accepted by javac, but Eclipse gives the error message Severity Description Resource In Folder Location Creation Time Id 2 Type mismatch: cannot convert from NonTerminalSourcePart<Tuple<Boolean,capture-of ? extends Term>> to NonTerminalSourcePart<? extends Tuple<Boolean,Term>> EclipseBug.java Jive-TUKL/src/jive/inout line 9 24. Februar 2006 01:08:28 1491705 I will attach the Java file.
Created attachment 35278 [details] Code to reproduce the described bug.
Reproduced
Added GenericTypeTest#test926 (disabled until resolved)
Markus - pls watch out for compiler evolution here to adjust refactoring side accordingly.
Thanks for the heads up. I'm interested in the proof why this should be legal. t.value().fst() is a 'capture-of ? extends Term', and a Tuple<Boolean, capture-of ? extends Term> cannot be assigned to a Tuple<Boolean, Term>. I'd say this is a bug in javac.
I will investigate. There are some tricky rules in this space. I am not saying this is a bug yet, I only acknowledged the fact our behavior differs from javac (1.5 and 1.6).
Interestingly, if slightly changing the code to cause both compilers to fail, error messages are revealing a few things: For line ##1: javac 1.6b71 says: X.java:7: incompatible types found : Tuple<capture of ? extends java.lang.Object,capture of ? extends java.lang.Number> required: java.lang.Number Number n = t.value(); ^ 1 error where we say: ---------- 1. ERROR in d:\X.java (at line 7) Number n = t.value(); ^^^^^^^^^ Type mismatch: cannot convert from capture-of ? extends Tuple<? extends Object,? extends Number> to Number ---------- public class X { public void foo() { NonTerminal<? extends Tuple<Boolean, Object>> RESULT = null; NonTerminal<? extends Tuple<? extends Object, ? extends Number>> t = null; Number n = t.value(); // ##1 RESULT = NonTerminal.create(Tuple.create(true, t.value().fst())); } } final class NonTerminal<V> { static <V> NonTerminal<V> create(final V _value) { return null; } final V value() { return null; } } class Tuple<A, B> { public static <A, B> Tuple<A, B> create(final A a, final B b) { return null; } public A fst() { return null; } }
I suspect the capture performed by javac isn't right. capture[NonTerminal<? extends Tuple<? extends Object, ? extends Number>>] should be: NonTerminal<capture-of ? extends Tuple<? extends Object, ? extends Number>> from which the invocation of #value() should extract the argument type: capture-of ? extends Tuple<? extends Object, ? extends Number> where it seems javac did only keep the upper bound Tuple, and reapplied capture.
Actually, we also do recursively capture the upper bound of the wildcard. So the upper bound of capture-of ? extends Tuple<? extends Object, ? extends Number> is actually: Tuple<capture-of ? extends Object, capture-of ? extends Number> This is mandated to not to expose wildcards to the outside (also see bug 111208).
Simpler testcase: import java.util.*; public class X { public void foo() { List<? extends List<Object>> RESULT = null; List<? extends Object> lst = null; RESULT = Collections.singletonList( Collections.singletonList( lst.get(0))); } }
Even simpler: import java.util.*; public class X { public void bar() { List<Object> RESULT = null; List<? extends Object> lst = null; RESULT = Collections.singletonList(lst.get(0)); } } javac says fine. We reject.
The following example demonstrates the bug in javac. Statement (1) is rejected. Statement (2) is accepted, though it is typed in the same way. For both, we say: Type mismatch: cannot convert from List<capture-of ? extends Object> to List<Object> Javac only complains against (1). import java.util.*; public class X { public void bar2(List<? extends Object> lst) { List<Object> RESULT = null; RESULT = lst; // 1 RESULT = Collections.singletonList(lst.get(0)); // 2 } } Closing as javac bug
Added GenericTypeTest#test927 too.
Bug in javac is that the generic method invocation is unwrapping "capture-of ? extends Object" into Object... Thus singletonList(<capture-of ? extends Object>) becomes List<Object> !?!?
Related javac bug is: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6384510
*** Bug 117119 has been marked as a duplicate of this bug. ***
(In reply to comment #12) > The following example demonstrates the bug in javac. > Statement (1) is rejected. > Statement (2) is accepted, though it is typed in the same way. This is wrong. (1) is rejected because List<? extends Object> is a supertype of List<Object>. Btw. List<? extends Object> is supertype of every List<X> where is X is a concrete type. In (2) type inference is appliead, which makes everything much more difficult, see §15.12.2.7 and $15.12.2.8 of the Java Language Specification (3rd edition). The second case creates a new list for which a type parameter T has to be inferred. It is not simply capture-of ? extends Object. > For both, we say: > Type mismatch: cannot convert from List<capture-of ? extends Object> to > List<Object> > > Javac only complains against (1). > > import java.util.*; > public class X { > public void bar2(List<? extends Object> lst) { > List<Object> RESULT = null; > RESULT = lst; // 1 > RESULT = Collections.singletonList(lst.get(0)); // 2 > } > } > > Closing as javac bug I.M.H.O javac bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6384510 is not related to this issue.
Yes it is. Inference has nothing to do here. Check signature of Collections.singletonList() public static <T> List<T> singletonList(T o) It guarantees it does wrapper the argument type into a List. Thus it should construct a List<capture...> equivalent to statement (1). If you disagree, pls go through a demonstration as to why type inference would legitimately lose the capture.
(In reply to comment #18) > Yes it is. Inference has nothing to do here. > Check signature of Collections.singletonList() > > public static <T> List<T> singletonList(T o) > > It guarantees it does wrapper the argument type into a List. Thus it should > construct a List<capture...> equivalent to statement (1). > > If you disagree, pls go through a demonstration as to why type inference would > legitimately lose the capture. > Hi, I hope we can agree that "public static <T> List<T> Collections.singletonList(T o)" is the only potentially applicable method in the sense of §15.12.2.1 of the JLS (3rd edition). Then, Phase 1 (§15.12.2.2) is started. singletonList is a generic method and because type parameters are not explicitly given type arguments inference is started. Type inference yields T == Object, as shown below. Then: (compare JLS §15.12.2.2 for notation) m == <T> List<T> singletonList(T o) e_1 == lst.get(0) A_1 == capture-of ? extends Objects == Z extends Object (Z is the fresh variable introduced by the wildcard capture) F_1 == T R_1 == T B_1 == Object U_1 == Object (This is assumed, see above. See the proof below) --> S_1 == T[T := U_1] == Object --> the following conditions have to hold: A_1 <: S_1 == Z extends Object <: Object == true U_1 <: B_1 == Object <: Object == true Therefore is m is the only method applicable by subtyping. Hence it is also the most specific one and it is used. Now, let me look at the type inference: It is started with the constraint: A_1 << T | by definition of A_1 ==> Z << T ==> T :> Z ==> EST(Z) == {Object} ==> EC == {Object} ==> MEC == EC ==> lub(Z) = Object ==> T = Object (This is the result of the type inference) Hope, we can agree on that. Regards, Jean-Marie
Well, not quite yet. <g> I agree with most of the demonstration which follows the spec. But the spec doesn't handle correctly the situation where only one candidate got inferred, and for which lub(Z) should yield Z (capture) instead of going through the erasure procees; which is only meant to compute an intersection type. There is no need to intersect here. My only change to your demonstration would be that: ==> lub(Z) = Z which means that the method invocation returns a List<capture-of ? extends Object> thus causing the code to be rejected. If your reasoning was true, then the following code would also need to be accepted, and javac also rejects it, like we do. import java.util.*; public class X { <U extends Object> void bar3(List<U> lst) { List<Object> RESULT = null; RESULT = lst; // 1 RESULT = Collections.singletonList(lst.get(0)); // 2 } } X.java:10: incompatible types found : java.util.List<U> required: java.util.List<java.lang.Object> RESULT = lst; // 1 ^ X.java:11: incompatible types found : java.util.List<U> required: java.util.List<java.lang.Object> RESULT = Collections.singletonList(lst.get(0)); // 2 I believe that according to your demonstration, statement (2) should have yielded back a List<Object>, assuming lub(U) --> Object. The spec has sometimes flaws as well...
> The spec has sometimes flaws as well... Of course and I can accept that the definition of lub is perhaps not correct as it is described in the spec. But, nevertheless the method call is well typed if I say explicitly that T should be Object. And, the spec says on p. 463 in the discussion, that type inference (a) does not affect soundness. (b) is designed to perform well in practice. It is a heuristic. Because javac accepts that code I would say it tries to get in the "recovery case" of type inference §15.12.2.8 as soon as it sees that the assignment is not well typed. Then the value for T is forgotten and a new attempt is started. If that attempt follows §15.12.2.8 T=Object follows by a completely different argumentation. Since, §15.12.2.8 is designed to handle the case that the result of the method is used in an assignment, it might be applicable. Regards, Jean-Marie
Please note that javac inferred List<U> for statement (2). The alternate strategy (based on return type expectation) would only yield List<Object>. I believe my interpretation is the right one, and that the spec for lub is not describing all scenarii. In particular, erasure is not to be performed as soon as there are more than one candidate. Type variables can be answered, and need to properly be discovered as possible supertypes of others. e.g. import java.util.*; public class X { <T> Map<T,T> foo(T t1, T t2) { return null; } <U extends Object, V extends U> void bar(U u, V v) { Map<Object,Object> map1 = foo(u, v); // KO Map<U,U> map2 = foo(u, v); // OK } }
(In reply to comment #20) > If your reasoning was true, then the following code would also need to be > accepted, and javac also rejects it, like we do. > import java.util.*; > public class X { > <U extends Object> void bar3(List<U> lst) { > List<Object> RESULT = null; > RESULT = Collections.singletonList(lst.get(0)); > } > } > > X.java:11: incompatible types > found : java.util.List<U> > required: java.util.List<java.lang.Object> > RESULT = Collections.singletonList(lst.get(0)); It is in fact a bit odd that Java does not allow the assignment of the singletonList to List<Integer> in the case where lst is a list with explicit type variable (U). But that's not really the point in our case. We agree that type inference should yield a "capture of ?" type for the method singletonList and that the language specification is somewhat incorrect in the definition of lub. The reason we agree to this is that "capture of" types where introduced to allow exactly such situations like: > public void bar2(List<? extends Object> lst) { > lst = Collections.singletonList(lst.get(0)); > } But we don't agree to what you said here: > The following example demonstrates the bug in javac. > Statement (1) is rejected. > Statement (2) is accepted, though it is typed in the same way. > > For both, we say: > Type mismatch: cannot convert from List<capture-of ? extends Object> to > List<Object> > > Javac only complains against (1). > > import java.util.*; > public class X { > public void bar2(List<? extends Object> lst) { > List<Object> RESULT = null; > RESULT = lst; // 1 > RESULT = Collections.singletonList(lst.get(0)); // 2 > } > } Especially the point > For both, we say: > Type mismatch: cannot convert from List<capture-of ? extends Object> to > List<Object> is where we disagree. Statement (1) is obviously wrong; we are trying to assign a given object to a variable of incompatible type, that's not ok :) In statement (2) however we are not trying to do anything wrong; we are especially not trying to create typing errors. Java will at first infer "capture of ?" for T which would result in a compile time error, since there can't be done any assignment conversion to satisfy the type of RESULT. But then javac instantiates the generic method > public static <T> List<T> Collections.singletonList(T o) with Object as T, which yields a legal assignment to RESULT and taking Object for T is legal, since the only parameter is in fact convertible to T (= Object), due to the upper bound of the implicit type variable. So there is nothing wrong with that, there is no need or obligation of javac to reject it. But as you pointed out by now: there is also no need or obligation of eclipse to accept it :) It just seems to be the case that javac's type inference heuristic is a bit more graceful than eclipse's. The "problem" is that we already have a large application utilizing the fact that javac can infer the correct type for T and eclipse cannot. We can workaround this fact by stating the type parameter T in our methods with the desired type, BUT: - this will extremely complicate the usage of our code, since the involved types aren't as short as "Object"... and - we will loose the ability to call our methods using static imports, since explicit statement of type variables for a method requires an identifier to be used. The language specification explicitly states: "The type inference algorithm should be viewed as a heuristic, designed to perf(d)orm well in practice. If it fails to infer the desired result, explicit type param(n)eters may be used instead". So it all boils down to the fact that type inference is a usability feature only and that javac, at the moment, behaves more usable than eclipse :-D Greets, Patrick Michel
> import java.util.*; > public class X { > public void bar2(List<? extends Object> lst) { > List<Object> RESULT = null; > RESULT = lst; // 1 > RESULT = Collections.singletonList(lst.get(0)); // 2 > } > } Again, and again, this is a bug in javac; and you should expect it to address it sooner or later. So in summary, I don't think it is more graceful, it simply has a bug which you happened to rely upon. I can call this method with a List<String>, and it will incorrectly allow its assignment into a List<Object>, though List<String> isn't assignment compatible with List<Object>. Statement (1) and (2) are both trying to assign the same incompatible type into List<Object>. The problem in statement (2) is that javac mishandles it, and thus is yielding back List<Object> instead of List<capture-of ? extends Object>. I think you should raise this to javac bug list.
(In reply to comment #24) > > import java.util.*; > > public class X { > > public void bar2(List<? extends Object> lst) { > > List<Object> RESULT = null; > > RESULT = lst; // 1 > > RESULT = Collections.singletonList(lst.get(0)); // 2 > > } > > } > Again, and again, this is a bug in javac; and you should expect it to address > it sooner or later. So in summary, I don't think it is more graceful, it simply > has a bug which you happened to rely upon. > > I can call this method with a List<String>, and it will incorrectly allow its > assignment into a List<Object>, though List<String> isn't assignment compatible > with List<Object>. > > Statement (1) and (2) are both trying to assign the same incompatible type into > List<Object>. The problem in statement (2) is that javac mishandles it, and > thus is yielding back List<Object> instead of List<capture-of ? extends > Object>. > > I think you should raise this to javac bug list. > Sorry, but I as well can only repeat myself... :-D > I can call this method with a List<String>, and it will incorrectly allow its > assignment into a List<Object>, though List<String> isn't assignment compatible > with List<Object>. No that's not true. In fact you can call this method with a List<String>, and it will correctly allow the first String to be taken out of the list, placed in a fresh List<Object> and THIS list to be assigned to a List<Object> reference. What happens is exactly what will happen when you explicitly state the methods type parameter to be Object, which is needed in eclipse and not needed for javac. Greets, Patrick Michel
Following your argument, how do you explain behavior observed in comment 22 ?
What I said so far: > It is in fact a bit odd that Java does not allow the assignment of the > singletonList to List<Object> in the case where lst is a list with explicit > type variable (U). But that's not really the point in our case. > So there is nothing wrong with that, there is no need or obligation of javac to > reject it. > But as you pointed out by now: there is also no need or obligation of eclipse > to accept it :) > The language specification explicitly states: > "The type inference algorithm should be viewed as a heuristic, designed to > perf(d)orm well in practice. If it fails to infer the desired result, explicit > type param(n)eters may be used instead". And again: My point is: Javac is NOT doing anything wrong. It is especially NOT generating typing errors! It is however NOT necessary that eclipse mimics this behavior, BUT it makes using eclipse a lot more tiresome than javac. Javac's behavior can be achieved in eclipse by explicitly stating the methods type variable: Collections.<Integer>singletonList(... That javac does not gracefully infer Object as the methods type parameter for explicitly given type variables is odd, but only states, that javac's heuristic does not find all possible cases too (that's why it's called a heuristic). I am sorry, but I fail to see your point. You keep talking of typing errors which can occur cause javac produces faulty code: I can't see this. Greets, Patrick Michel
> Collections.<Integer>singletonList(... This is the second time I write Integer instead of Object; please switch all occurrences of Integer to Object in my postings :-D We talked a LOT about this problem and had so many example codes; I can't help but mix up those two... :)
It is an interesting discussion, so I will not complain. Don't worry. Playing more with an example, I wrote the following which should be equivalent to what we have been debating recently (unless I got confused). I used a Box in place of a List, so I could specify some upper bounds on the type parameter (as opposed to plain Object for List scenario). Interestingly, javac now rejects it as well, like we do. import java.util.*; public class X { public void bar2(Box<?> b) { Box<Runnable> bx = box(b.element); // KO box(b.element).element.run(); } static <U extends Runnable> Box<U> box(U u) { return new Box<U>(u); } } class Box<E extends Runnable> { E element; Box(E element) { this.element = element; } }
> Interestingly, javac now rejects it as well, like we do. Ok, so you constructed another example where eclipse as well as javac can't infer a type for the type variable U of method box, which would make the program compile AND perfectly valid: If you use X.<Runnable>box(... in the "KO" line, the program compiles and will not produce any type errors, as it's absolutely ok to want b to be wrapped in a Box<Runnable>. (Let me stress this point once more, since that really is the core of all this: box is a generic method with parameter U, which has no compile time errors. There IS a possible type (Runnable) to choose for U, which satisfies the method parameters AND the assignment. The language specification however does NOT demand this type to be found, as it is not the correct guess when inferring the type from it's parameters only! When following the type inference algorithm, it will therefore eventually not find this type. When reading the algorithm, there are cases in which it considers the type on the left side of an assignment and therefore is able to find! So if a compiler manages to find the type and compiles the program with it and yields no errors, it is NOT doing anything wrong, but is instead helping the user, as he does not need to annotate that type on every call of the method. Explicitly annotating type parameters for generic methods is in multiple ways inconvenient, and it's not the last reason the java language uses type inference at all (as stated in the language spec itself!), instead of requiring the user to always annotate the type he wants.) So again I don't know what your point is :) We already agreed, that we are not talking about a potential eclipse bug in this thread, but merely about the fact, that javac manages to find the "correct" (or intended) type variable for generic methods in more cases than eclipse. We also agreed that eclipse does not need to be able to infer intended type variables in all cases in which the javac can infer a type, since the language specification does not demand it for the cases we consider. What javac does is therefore not to be considered a bug. If at all the behavior of javac prevents flawless migration from javac to eclipse or other compilers, by offering features, which are not demanded by the language spec. Only if this is considered to be harmful javac will change it's behavior, but I doubt it at the moment. :) For our part this javac behavior is extremely convenient, as the static methods in question where introduced for the sole purpose of using the compilers type inference. Explicit annotation of types is extremely tiresome with generic types that extend over one visible line of editor space :-D So the question is if eclipse is willing to support this case of type inference in it's heuristic or not.
It is not, unless the spec made it clear. Remember the inference algorithm performs in 2 stages. The expected return type (when assignment conversion is performed) is only considered when the argument type inference is leaving some type variables unconstrained. This is not the case here. Note that if there were not argument to the box method, it would infer using return type, and meet what you intend. I already argued to the spec lead that inference could be made smarter by injecting all constraints at once, i.e. consider the expected return type and type parameter bounds initially. They decided not (don't ask me why). I would also disagree that the behavior you are seeing is unspecified. You make it sound like it is a courtesy of the compiler, not an obligation. It is in most cases, but the spec decided that in some more advanced scenarii, it shouldn't try to hard, and therefore they offered the explicit parameterization syntax to have the user tell what he wants. Again, this is not our decision; and I already expressed opposite wishes. But in the end, that is what they decided. From the information I have, javac will align onto us soon; and the inconsistent behavior you are seeing it just a leftover of some hack they did put in at the time captures where not properly implemented. Now you may still disagree that the spec is wrong, and that inference should be smarter, and I would follow you there. My only point is that our behavior is mandated by the current spec, and that the discrepancy with javac is only a temporary one, which is soon going to be resolved. The fact you relied on it is unfortunate, but this is nothing Eclipse did cause to you. I would suggest you raise this discussion in Java generics forum, to see what others think. http://forum.java.sun.com/forum.jspa?forumID=316&start=0
OK! Now we are talking about the same thing :) I agree with what you said in your last post. It will however be interesting to know what sun thinks about it; if the spec is once more not really precise at this point. Our feeling that nothing harmful is done by the compiler is in some way backed up by the intention of the spec, though the existing algorithm does not yield such results (though the algorithm is already proven to contain flaws at most important places... :-)). We also acknowledge the need of a precise definition what a reference compiler should accept or reject. It will especially be interesting to see if javac changes it's behavior in future releases :) Greets, Patrick Michel
FYI - javac 1.6b74 seems to have aligned with the Eclipse compiler.
New jdk bugs got filed: javac bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6557661 specification bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6557665
Changing OS from Mac OS to Mac OS X as per bug 185991