Type inference: bug or feature?

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Jul 27 10:26:40 UTC 2020


CC'ing compiler-dev

Hi Justin,
the behavior you are observing is normal. For a Java expression to be 
able to be influenced by type inference it has to be a poly expression 
(where poly stands for _many_ as in, the same expression can have many 
types).

At the time we did Java 8 we briefly considered making cast expressions 
part of the poly expression dance (like lambdas, method references, 
method calls, parens, new creation expression, conditionals and switch 
expression - see JLS 15.2), but the rule which dictate cast conversion 
(JLS 5.5) are so complex (as they have to take into account possibility 
for unchecked cast, etc.) that it felt like touching them was a very 
risky move, with no clear benefit.

The behavior you see is caused by the fact that the cast expression acts 
as a "shield" - that is, whenever a method call appears after a cast 
expression (as in your case), the method call is type-checked as if it 
were in isolation, and _then_ the result of type checking is validated 
against the cast. In other words, your example is no different than doing:

var x = (List<Integer>)(Object)emptyList(Number.class);


That is, the emptyList call will see no meaningful target type (just 
Object), so Number will be inferred and a List<Number> will be returned, 
which will then be incompatible with the type of the cast expression 
(List<Integer>).

Your second set of examples, since it does not make use of cast 
expressions, works as expected, as the target type can freely flow 
inside the method call typing, and thus influence the type inference 
result (e.g. the inference engine now sees two constraints, for Integer 
and for Number, and is of course able to pick the "best" one).

Hope this helps.

Cheers
Maurizio

On 26/07/2020 18:22, Justin Dekeyser wrote:
> Dear all,
>
> I'm not sure but I think I've found a bug in Java type inference mechanism.
> It may also be a feature, so I'll expose the problem to you below; in terms
> of Integer, Number and List, although you'll quickly realize it will work
> wrong in any similar situation.
>
> Let's assume I have
>
> static <U, V extends U> List<U> emptyList(Class<U> magnet) {
>     return Collections.emptyList();
> }
>
> Then the following codes does not compile (for the same reason):
>
> var x = (List<Integer>) emptyList(Number.class);
> List<Integer> x = (List<Integer>) emptyList(Number.class);
>
> incompatible types: List<Number> cannot be converted to List<Integer>
>
> however, the following do compile:
>
> var x = emptyList(Number.class); // inferred to List<Number>
> List<Integer> x = emptyList(Number.class); // no mistake here, it's Integer
> on the left
>
> Is this the expected behavior? Why does casting operation here interfere
> with type inference like this?
>
> Regards,
>
> Justin Dekeyser
>
>
>
>
> <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail>
> Garanti
> sans virus. www.avast.com
> <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail>
> <#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>


More information about the core-libs-dev mailing list