Generics type inference problem?

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Jun 12 22:51:27 UTC 2017



On 12/06/17 20:28, Attila Kelemen wrote:
> 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)?
The JLS is generally more rigid than the compiler in this area. While 
it's possible to ameliorate the situation with some fixes, our long term 
goal is to arrive at a place where the compiler (all of them, not just 
javac) and the spec agree on whether these kind of programs should 
compile. But this require some work on the spec side too, which we will 
take care of post 9.

Thanks
Maurizio
>
> 2017-06-12 16:25 GMT+02:00 Maurizio Cimadamore 
> <maurizio.cimadamore at oracle.com <mailto: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
>     <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
>     <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/0659c47c/attachment-0001.html>


More information about the compiler-dev mailing list