Generics type inference problem?

Attila Kelemen attila.kelemen85 at gmail.com
Mon Jun 12 19:28:00 UTC 2017


Forgot to add the dev-list. Sorry for the duplicate message.

Thank you very much for the detailed explanation. I did not expect this to
be fixed for the release of Java 9 (unless it turns out to be a bug, which
I was hinted against). Aside from that: How rigid the JLS is in this
regard? Is it acceptable to adjust type inference - in a post release
update - in a way that may make some code compile which failes to do so
according to the spec (assuming the construct can be proven to be safe)?

2017-06-12 16:25 GMT+02:00 Maurizio Cimadamore <
maurizio.cimadamore at oracle.com>:

> Hi Attila,
> the change in behavior you are observing is expected and part of the fix
> for:
>
> https://bugs.openjdk.java.net/browse/JDK-8039214
>
> Let me try to show you what the difference is between 8 and 9 - and let me
> doing so by rewriting your example to use unique type-variable names:
>
> class NewClass {
>         private static <T extends Throwable> List<TestFactory<T>> test(
>                 Collection<? extends Supplier<? extends T>> factory) {
>             return factory.stream()
>                     .map(NewClass::testMap)
>                     .collect(Collectors.toList());
>         }
>
>         private static <E extends Throwable> TestFactory<E>
> testMap(Supplier<? extends E> factory) {
>             return null;
>         }
>
>         private static interface TestFactory<E extends Throwable> {
>         }
>     }
>
> So, the variable 'factory' has this static type:
>
> Collection<? extends Supplier<? extends T>>
>
> If we want to access its members (e.g. call stream() ), we need to capture
> the type first, and obtain:
>
> Collection<#CAP1>,
>
> where #CAP1 is a fresh type-variable such that #CAP1 <: Supplier<? extends
> T>
>
> Now, the instantiated type of 'factory.stream().map()' is something like:
>
> <R> R map(Function<? super #CAP1, ? extends R>)
>
> This means that when checking the method reference expression:
>
> NewClass::testMap
>
> We will be using #CAP1 as the argument type (see JLS 15.13.2). In other
> words, we will run a method lookup for a method name 'testMap' inside class
> 'NewClass' with argument types { #CAP1 }. This triggers an applicability
> check against the only method found (NewClass.testMap); as part of the
> applicability check we need to check that the actual argument type is
> compatible with the formal - so:
>
> #CAP1 <: Supplier<? extends E>
>
> To do this subtyping we need to rewrite the left hand side so that it
> looks like a 'Supplier' - we do so by recursing on the upper bound:
>
> Supplier<? extends T> <: Supplier<? extends E>
>
> And, to do this subtyping, we need to capture the left hand side (at least
> that's how javac does it) - as recursion into supertypes can sometimes
> require capture conversion. So:
>
> Supplier<#CAP2> <: Supplier<? extends E>, where #CAP2 <: T
>
> which is satisfied if:
>
> #CAP2 <= E
>
> (where '<=' means 'contained by').
>
> Now, in JDK 8's javac, this containment relation was rewritten as follows:
>
> T <: E (that is, we recursed on the upper bound of the left hand side.
>
> This led to an inference constraint which ultimately led to E and T being
> solved in the same way, and the program compiled without errors.
>
> This behavior is flexible but, ultimately, unsound - JDK-8039214 fixed
> that, so that in 9 the type containment is rewritten as:
>
> #CAP2 <: E (no recursion on the left hand side).
>
> This then runs into trouble because when we need to solve the inference
> variable E, we have two incompatible constraints:
>
> E = T (from target type context of the collect() call)
>
> and
>
> E = #CAP2 (from above)
>
> hence the error.
>
>
> We worked on some potential mitigation for these issues, but ultimately
> they have been considered too risky for targeting JDK 9 - you can follow
> the issue here:
>
> https://bugs.openjdk.java.net/browse/JDK-8160244
>
> Thanks!
>
> Maurizio
>
>
>
> On 11/06/17 17:16, Attila Kelemen wrote:
>
>> Hi,
>>
>> I'm not sure if my issue is a violation of the specs or a bug, so I'm
>> writing here instead of a bug report. The following class does not compile
>> (imports are omitted) in Java 9 but does in Java 8:
>>
>>     public final class NewClass {
>>         private static <E extends Throwable> List<TestFactory<E>> test(
>>                 Collection<? extends Supplier<? extends E>> factory) {
>>             return factory.stream()
>>                     .map(NewClass::testMap)
>>                     .collect(Collectors.toList());
>>         }
>>
>>         private static <E extends Throwable> TestFactory<E>
>> testMap(Supplier<? extends E> factory) {
>>             return null;
>>         }
>>
>>         private static interface TestFactory<E extends Throwable> {
>>         }
>>     }
>>
>> The above `test` method does not compile because type inference fails to
>> deduce the "appropriate" type for the return value of `Stream.collect`.
>> Which is strange given that `Stream.map` should map to
>> `Stream<TestFactory<E>>`. In fact, if I rewrite the method `test` to:
>>
>>         Stream<TestFactory<E>> factoryStream =
>> factory.stream().map(NewClass::testMap);
>>         return factoryStream.collect(Collectors.toList());
>>
>> The code will compile fine.
>>
>> Is this behaviour in accordance with the spec or is this a bug?
>>
>> The exact versions I have tried this with:
>>
>> Java 8: java version "1.8.0_92", build 1.8.0_92-b14
>> Java 9: java version "9-ea", build 9-ea+170
>>
>> Thanks,
>> Attila Kelemen
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20170612/10911fda/attachment.html>


More information about the compiler-dev mailing list