JDK-8030017 : Assignments allow widening before unboxing
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 6u65,7u45
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2013-12-12
  • Updated: 2016-09-21
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbd_majorUnresolved
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
The specification for assignment contexts (JLS 5.2) allows an unboxing conversion followed by a widening primitive conversion.  It does _not_ allow a widening reference conversion before this occurs.  But javac is permitting it.

The following is allowed:

<T extends Integer> void test(T arg) {
 int i = arg; // JLS: error; javac: ok
}

But not the complementary boxing case:

void test2(int i) {
 Long l = i; // JLS: error; javac: error
}

Comments
The call to 'cvarUpperBound' in Attr.visitForeachLoop is to account for a capture variable with an array-typed upper bound. Something like this: interface I<T> { T get(); } void test(I<? extends String[]> arg) { for (String s : arg.get()) {} } JLS claims (with some ambiguity) that this is an error: the type of 'arg.get()' is a capture variable, not an array type. JDK-8013843 suggests changing the specification. If that change is made, then we'd probably want an 'asArray' call that parallels 'asSuper' -- find the supertype of T that is an array type -- and could get rid of this 'cvarUpperBound' hack.
21-09-2016

Spec problems with the for-each loop are also addressed by JDK-8166421.
21-09-2016

After JDK-8166326 gets resolved, this can be revisited to see if any work is called for.
20-09-2016

Considering the above discussion, I think it's best to change JLS: see JDK-8166326.
19-09-2016

Maurizio, While working on this, I found a couple of suspect pieces of code. Can you take a look and comment on them: (1) com.sun.tools.javac.comp.Attr#visitForeachLoop: line 1127 reads: Type exprType = types.cvarUpperBound(attribExpr(tree.expr, loopEnv)); I think this should be: Type exprType = attribExpr(tree.expr, loopEnv); i.e I don't think tree.expr's type can ever be a captured type. It can be a parameterized type which captured type arguments. (2) com.sun.tools.javac.code.Types#cvarUpperBound reads: public Type cvarUpperBound(Type t) { if (t.hasTag(TYPEVAR)) { TypeVar v = (TypeVar) t; return v.isCaptured() ? cvarUpperBound(v.bound) : v; } else return t; } I think the recursive call is not meaningful, as capture is always applied only to one level ? At least at first sight these look suspicious. I massaged these pieces of code and ran langtools tests and all pass.
17-03-2015

More related cases from langtools test suite: class Box<T> { T get() { return null; } } class Main { public static void main(String[] args) { Box<? extends Integer> bi = null; int i = bi.get(); } } It looks like there could be some non-trivial amount of code that would see some impact - as these examples are culled from real bug reports presumably. test/tools/javac/generics/inference/6240565/T6240565.java and test/tools/javac/generics/wildcards/pos/RvalConversion.java are a couple of other examples that "fail" with my attempted change: that changes the method com.sun.tools.javac.code.Types#unboxedType from being: public Type unboxedType(Type t) { for (int i=0; i<syms.boxedName.length; i++) { Name box = syms.boxedName[i]; if (box != null && asSuper(t, syms.enterClass(box)) != null) return syms.typeOfTag[i]; } return Type.noType; } into: public Type unboxedType(Type t) { // 5.1.8 defines unboxing for exactly 8 boxed types. if (t.hasTag(CLASS)) { for (int i=0; i<syms.boxedName.length; i++) { Name box = syms.boxedName[i]; if (box != null && isSameType(t, syms.enterClass(box).type)) return syms.typeOfTag[i]; } } return Type.noType; }
17-03-2015

@Dan, can you take a look at 14.14.2. In particular, The enhanced for statement is equivalent to a basic for statement of the form: for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} TargetType Identifier =(TargetType) #i.next(); Statement } ... If the declared type of the local variable in the header of the enhanced for statement is a reference type, then TargetType is that declared type; otherwise, TargetType is the upper bound of the capture conversion (��5.1.10) of the type argument of I , or Object if I is raw. ... For example, this code: List<? extends Integer> l = ... for (float i : l) ... will be translated to: for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) { float #i0 = (Integer)#i.next(); --------------- The last translation does not quite adhere to the schema outlined initially. Should the identifier's declared type have been retained in the scheme as is done in the example (i.e float) As far as javac's type checking in this example goes, it looks like the wording clearly says we should check whether Integer is assignable to int, but javac ATM is checking if T extends Integer is assignable to int.
17-03-2015

Need to check what happens to code of this form, which presently compiles: import java.util.*; public class X { static <T extends Integer> void g(Iterable<T> b) { for ( int i : b ) { System.out.println(i); } }
17-03-2015